diff --git a/react/features/app/middlewares.web.js b/react/features/app/middlewares.web.js index cc3c2250b..c3c5b6830 100644 --- a/react/features/app/middlewares.web.js +++ b/react/features/app/middlewares.web.js @@ -23,5 +23,6 @@ import '../virtual-background/middleware'; import '../face-landmarks/middleware'; import '../gifs/middleware'; import '../whiteboard/middleware'; +import '../base/dialog/middleware'; import './middlewares.any'; diff --git a/react/features/base/dialog/middleware.web.ts b/react/features/base/dialog/middleware.web.ts new file mode 100644 index 000000000..d2e17ee33 --- /dev/null +++ b/react/features/base/dialog/middleware.web.ts @@ -0,0 +1,31 @@ +import ChatPrivacyDialog from '../../chat/components/web/ChatPrivacyDialog'; +import DisplayNamePrompt from '../../display-name/components/web/DisplayNamePrompt'; +import EmbedMeetingDialog from '../../embed-meeting/components/EmbedMeetingDialog'; +import KeyboardShortcutsDialog from '../../keyboard-shortcuts/components/web/KeyboardShortcutsDialog'; +import MiddlewareRegistry from '../redux/MiddlewareRegistry'; + +import { OPEN_DIALOG } from './actionTypes'; + +// ! IMPORTANT - This whole middleware is only needed for the transition from from @atlaskit dialog to our component. +// ! It should be removed when the transition is over. + +const NEW_DIALOG_LIST = [ KeyboardShortcutsDialog, ChatPrivacyDialog, DisplayNamePrompt, EmbedMeetingDialog ]; + +// This function is necessary while the transition from @atlaskit dialog to our component is ongoing. +const isNewDialog = (component: any) => NEW_DIALOG_LIST.some(comp => comp === component); + +/** + * Implements the entry point of the middleware of the feature base/media. + * + * @param {IStore} store - The redux store. + * @returns {Function} + */ +MiddlewareRegistry.register(() => (next: Function) => (action: any) => { + switch (action.type) { + case OPEN_DIALOG: { + action.isNewDialog = isNewDialog(action.component); + } + } + + return next(action); +}); diff --git a/react/features/base/dialog/reducer.ts b/react/features/base/dialog/reducer.ts index b1b019dd6..eefa3224a 100644 --- a/react/features/base/dialog/reducer.ts +++ b/react/features/base/dialog/reducer.ts @@ -13,6 +13,7 @@ import { export interface IDialogState { component?: ComponentType; componentProps?: Object; + isNewDialog?: boolean; sheet?: ComponentType; sheetProps?: Object; } @@ -43,7 +44,8 @@ ReducerRegistry.register('features/base/dialog', (state = {}, acti case OPEN_DIALOG: return assign(state, { component: action.component, - componentProps: action.componentProps + componentProps: action.componentProps, + isNewDialog: action.isNewDialog }); case HIDE_SHEET: diff --git a/react/features/base/ui/components/web/Dialog.tsx b/react/features/base/ui/components/web/Dialog.tsx index 46adcfa74..11d0b41ae 100644 --- a/react/features/base/ui/components/web/Dialog.tsx +++ b/react/features/base/ui/components/web/Dialog.tsx @@ -1,9 +1,11 @@ import { Theme } from '@mui/material'; import React, { ReactElement, useCallback, useContext, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; +import { useDispatch } from 'react-redux'; import { keyframes } from 'tss-react'; import { makeStyles } from 'tss-react/mui'; +import { hideDialog } from '../../../dialog/actions'; import { IconClose } from '../../../icons/svg'; import { withPixelLineHeight } from '../../../styles/functions.web'; @@ -170,14 +172,18 @@ const useStyles = makeStyles()((theme: Theme) => { }); interface DialogProps { - cancelKey?: string; + cancel?: { + hidden?: boolean; + translationKey?: string; + }; children?: ReactElement | ReactElement[]; description?: string; ok?: { disabled?: boolean; - key: string; + hidden?: boolean; + translationKey?: string; }; - onCancel: () => void; + onCancel?: () => void; onSubmit?: () => void; size?: 'large' | 'medium'; title?: string; @@ -185,26 +191,37 @@ interface DialogProps { } const Dialog = ({ - title, - titleKey, - description, - size = 'medium', - onCancel, + cancel = { translationKey: 'dialog.Cancel' }, children, - ok, - cancelKey, - onSubmit + description, + ok = { translationKey: 'dialog.Ok' }, + onCancel, + onSubmit, + size = 'medium', + title, + titleKey }: DialogProps) => { const { classes, cx } = useStyles(); const { t } = useTranslation(); const { isUnmounting } = useContext(DialogTransitionContext); + const dispatch = useDispatch(); + + const onClose = useCallback(() => { + onCancel?.(); + dispatch(hideDialog()); + }, [ onCancel ]); const handleKeyDown = useCallback((e: KeyboardEvent) => { if (e.key === 'Escape') { - onCancel(); + onClose(); } }, []); + const submit = useCallback(() => { + onSubmit?.(); + dispatch(hideDialog()); + }, [ onSubmit ]); + useEffect(() => { window.addEventListener('keydown', handleKeyDown); @@ -215,7 +232,7 @@ const Dialog = ({
+ onClick = { onClose } />
+ onClick = { onClose } />
{children}
- {cancelKey &&
diff --git a/react/features/base/ui/components/web/DialogContainer.tsx b/react/features/base/ui/components/web/DialogContainer.tsx index adbb48b1a..b42d51bc1 100644 --- a/react/features/base/ui/components/web/DialogContainer.tsx +++ b/react/features/base/ui/components/web/DialogContainer.tsx @@ -2,7 +2,6 @@ import { ModalTransition } from '@atlaskit/modal-dialog'; import React, { Component, ComponentType } from 'react'; import { IState } from '../../../../app/types'; -import KeyboardShortcutsDialog from '../../../../keyboard-shortcuts/components/web/KeyboardShortcutsDialog'; import { ReactionEmojiProps } from '../../../../reactions/constants'; import { connect } from '../../../redux/functions'; @@ -20,6 +19,11 @@ interface Props { */ _componentProps: Object; + /** + * Whether the dialog is using the new component. + */ + _isNewDialog: boolean; + /** * Array of reactions to be displayed. */ @@ -31,53 +35,12 @@ interface Props { _reducedUI: boolean; } -// This function is necessary while the transition from @atlaskit dialog to our component is ongoing. -const isNewDialog = (component: any) => { - const list = [ KeyboardShortcutsDialog ]; - - return Boolean(list.find(comp => comp === component)); -}; - -// Needed for the transition to our component. -type State = { - isNewDialog: boolean; -}; - /** * Implements a DialogContainer responsible for showing all dialogs. Necessary * for supporting @atlaskit's modal animations. * */ -class DialogContainer extends Component { - - /** - * Initializes a new {@code DialogContainer} instance. - * - * @param {Props} props - The React {@code Component} props to initialize - * the new {@code DialogContainer} instance with. - */ - constructor(props: Props) { - super(props); - this.state = { - isNewDialog: false - }; - } - - /** - * Check which Dialog container to render. - * Needed during transition from atlaskit. - * - * @inheritdoc - * @returns {void} - */ - componentDidUpdate(prevProps: Props) { - if (this.props._component && prevProps._component !== this.props._component) { - // eslint-disable-next-line react/no-did-update-set-state - this.setState({ - isNewDialog: isNewDialog(this.props._component) - }); - } - } +class DialogContainer extends Component { /** * Returns the dialog to be displayed. @@ -104,7 +67,7 @@ class DialogContainer extends Component { * @returns {ReactElement} */ render() { - return this.state.isNewDialog ? ( + return this.props._isNewDialog ? ( {this._renderDialogContent()} @@ -131,6 +94,7 @@ function mapStateToProps(state: IState) { return { _component: stateFeaturesBaseDialog.component, _componentProps: stateFeaturesBaseDialog.componentProps, + _isNewDialog: stateFeaturesBaseDialog.isNewDialog, _reducedUI: reducedUI }; } diff --git a/react/features/chat/components/AbstractChatPrivacyDialog.js b/react/features/chat/components/AbstractChatPrivacyDialog.tsx similarity index 75% rename from react/features/chat/components/AbstractChatPrivacyDialog.js rename to react/features/chat/components/AbstractChatPrivacyDialog.tsx index 2d6a428ea..22275e80d 100644 --- a/react/features/chat/components/AbstractChatPrivacyDialog.js +++ b/react/features/chat/components/AbstractChatPrivacyDialog.tsx @@ -1,42 +1,40 @@ -// @flow - import { PureComponent } from 'react'; +import { WithTranslation } from 'react-i18next'; -import { getParticipantById } from '../../base/participants'; +import { IState, IStore } from '../../app/types'; +import { getParticipantById } from '../../base/participants/functions'; +import { Participant } from '../../base/participants/types'; +// eslint-disable-next-line lines-around-comment +// @ts-ignore import { sendMessage, setPrivateMessageRecipient } from '../actions'; -type Props = { - - /** - * The message that is about to be sent. - */ - message: Object, - - /** - * The ID of the participant that we think the message may be intended to. - */ - participantID: string, - - /** - * Function to be used to translate i18n keys. - */ - t: Function, +interface Props extends WithTranslation { /** * Prop to be invoked on sending the message. */ - _onSendMessage: Function, + _onSendMessage: Function; /** * Prop to be invoked when the user wants to set a private recipient. */ - _onSetMessageRecipient: Function, + _onSetMessageRecipient: Function; /** * The participant retrieved from Redux by the participanrID prop. */ - _participant: Object -}; + _participant: Object; + + /** + * The message that is about to be sent. + */ + message: Object; + + /** + * The ID of the participant that we think the message may be intended to. + */ + participantID: string; +} /** * Abstract class for the dialog displayed to avoid mis-sending private messages. @@ -54,8 +52,6 @@ export class AbstractChatPrivacyDialog extends PureComponent { this._onSendPrivateMessage = this._onSendPrivateMessage.bind(this); } - _onSendGroupMessage: () => boolean; - /** * Callback to be invoked for cancel action (user wants to send a group message). * @@ -67,8 +63,6 @@ export class AbstractChatPrivacyDialog extends PureComponent { return true; } - _onSendPrivateMessage: () => boolean; - /** * Callback to be invoked for submit action (user wants to send a private message). * @@ -90,13 +84,13 @@ export class AbstractChatPrivacyDialog extends PureComponent { * @param {Function} dispatch - The Redux dispatch function. * @returns {Props} */ -export function _mapDispatchToProps(dispatch: Function): $Shape { +export function _mapDispatchToProps(dispatch: IStore['dispatch']) { return { _onSendMessage: (message: Object) => { dispatch(sendMessage(message, true)); }, - _onSetMessageRecipient: participant => { + _onSetMessageRecipient: (participant: Participant) => { dispatch(setPrivateMessageRecipient(participant)); } }; @@ -105,11 +99,11 @@ export function _mapDispatchToProps(dispatch: Function): $Shape { /** * Maps part of the Redux store to the props of this component. * - * @param {Object} state - The Redux state. + * @param {IState} state - The Redux state. * @param {Props} ownProps - The own props of the component. * @returns {Props} */ -export function _mapStateToProps(state: Object, ownProps: Props): $Shape { +export function _mapStateToProps(state: IState, ownProps: Props) { return { _participant: getParticipantById(state, ownProps.participantID) }; diff --git a/react/features/chat/components/web/ChatPrivacyDialog.js b/react/features/chat/components/web/ChatPrivacyDialog.tsx similarity index 65% rename from react/features/chat/components/web/ChatPrivacyDialog.js rename to react/features/chat/components/web/ChatPrivacyDialog.tsx index 837d396ca..bf8046899 100644 --- a/react/features/chat/components/web/ChatPrivacyDialog.js +++ b/react/features/chat/components/web/ChatPrivacyDialog.tsx @@ -1,10 +1,8 @@ -/* @flow */ - import React from 'react'; -import { Dialog } from '../../../base/dialog'; -import { translate } from '../../../base/i18n'; -import { connect } from '../../../base/redux'; +import { translate } from '../../../base/i18n/functions'; +import { connect } from '../../../base/redux/functions'; +import Dialog from '../../../base/ui/components/web/Dialog'; import { AbstractChatPrivacyDialog, _mapDispatchToProps, _mapStateToProps } from '../AbstractChatPrivacyDialog'; /** @@ -20,22 +18,17 @@ class ChatPrivacyDialog extends AbstractChatPrivacyDialog { render() { return ( + titleKey = 'dialog.sendPrivateMessageTitle'>
{ this.props.t('dialog.sendPrivateMessage') }
); } - - _onSendGroupMessage: () => boolean; - - _onSendPrivateMessage: () => boolean; } export default translate(connect(_mapStateToProps, _mapDispatchToProps)(ChatPrivacyDialog)); diff --git a/react/features/display-name/components/web/DisplayNamePrompt.tsx b/react/features/display-name/components/web/DisplayNamePrompt.tsx index 609c5c839..90b88eba3 100644 --- a/react/features/display-name/components/web/DisplayNamePrompt.tsx +++ b/react/features/display-name/components/web/DisplayNamePrompt.tsx @@ -1,9 +1,8 @@ import React from 'react'; -// @ts-ignore -import { Dialog } from '../../../base/dialog'; import { translate } from '../../../base/i18n/functions'; import { connect } from '../../../base/redux/functions'; +import Dialog from '../../../base/ui/components/web/Dialog'; import Input from '../../../base/ui/components/web/Input'; import AbstractDisplayNamePrompt, { Props } from '../AbstractDisplayNamePrompt'; @@ -52,10 +51,10 @@ class DisplayNamePrompt extends AbstractDisplayNamePrompt { render() { return ( + titleKey = 'dialog.displayNameRequired'> + cancel = {{ hidden: true }} + ok = {{ hidden: true }} + titleKey = { 'embedMeeting.title' }>