ref(ui-components) Update some dialogs to use the new component (#12288)

Improve Dialog component to auto close on cancel or on submit
Change logic to determine whether a dialog is using the new component
Convert some files to TS
This commit is contained in:
Robert Pintilii 2022-10-04 12:44:48 +03:00 committed by GitHub
parent 21bcbdc12f
commit f4f8808d95
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 132 additions and 155 deletions

View File

@ -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';

View File

@ -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);
});

View File

@ -13,6 +13,7 @@ import {
export interface IDialogState {
component?: ComponentType;
componentProps?: Object;
isNewDialog?: boolean;
sheet?: ComponentType;
sheetProps?: Object;
}
@ -43,7 +44,8 @@ ReducerRegistry.register<IDialogState>('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:

View File

@ -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 = ({
<div className = { cx(classes.container, isUnmounting && 'unmount') }>
<div
className = { classes.backdrop }
onClick = { onCancel } />
onClick = { onClose } />
<div
aria-describedby = { description }
aria-labelledby = { title ?? t(titleKey ?? '') }
@ -227,20 +244,20 @@ const Dialog = ({
<ClickableIcon
accessibilityLabel = { t('dialog.close') }
icon = { IconClose }
onClick = { onCancel } />
onClick = { onClose } />
</div>
<div className = { classes.content }>{children}</div>
<div className = { classes.footer }>
{cancelKey && <Button
accessibilityLabel = { t(cancelKey) }
labelKey = { cancelKey }
onClick = { onCancel }
{!cancel.hidden && <Button
accessibilityLabel = { t(cancel.translationKey ?? '') }
labelKey = { cancel.translationKey }
onClick = { onClose }
type = 'tertiary' />}
{ok && <Button
accessibilityLabel = { t(ok.key) }
{!ok.hidden && <Button
accessibilityLabel = { t(ok.translationKey ?? '') }
disabled = { ok.disabled }
labelKey = { ok.key }
onClick = { onSubmit } />}
labelKey = { ok.translationKey }
onClick = { submit } />}
</div>
</div>
</div>

View File

@ -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<Props, State> {
/**
* 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<Props> {
/**
* Returns the dialog to be displayed.
@ -104,7 +67,7 @@ class DialogContainer extends Component<Props, State> {
* @returns {ReactElement}
*/
render() {
return this.state.isNewDialog ? (
return this.props._isNewDialog ? (
<DialogTransition>
{this._renderDialogContent()}
</DialogTransition>
@ -131,6 +94,7 @@ function mapStateToProps(state: IState) {
return {
_component: stateFeaturesBaseDialog.component,
_componentProps: stateFeaturesBaseDialog.componentProps,
_isNewDialog: stateFeaturesBaseDialog.isNewDialog,
_reducedUI: reducedUI
};
}

View File

@ -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<Props> {
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<Props> {
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<Props> {
* @param {Function} dispatch - The Redux dispatch function.
* @returns {Props}
*/
export function _mapDispatchToProps(dispatch: Function): $Shape<Props> {
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<Props> {
/**
* 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<Props> {
export function _mapStateToProps(state: IState, ownProps: Props) {
return {
_participant: getParticipantById(state, ownProps.participantID)
};

View File

@ -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 (
<Dialog
cancelKey = 'dialog.sendPrivateMessageCancel'
okKey = 'dialog.sendPrivateMessageOk'
cancel = {{ translationKey: 'dialog.sendPrivateMessageCancel' }}
ok = {{ translationKey: 'dialog.sendPrivateMessageOk' }}
onCancel = { this._onSendGroupMessage }
onSubmit = { this._onSendPrivateMessage }
titleKey = 'dialog.sendPrivateMessageTitle'
width = 'small'>
titleKey = 'dialog.sendPrivateMessageTitle'>
<div>
{ this.props.t('dialog.sendPrivateMessage') }
</div>
</Dialog>
);
}
_onSendGroupMessage: () => boolean;
_onSendPrivateMessage: () => boolean;
}
export default translate(connect(_mapStateToProps, _mapDispatchToProps)(ChatPrivacyDialog));

View File

@ -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<State> {
render() {
return (
<Dialog
isModal = { false }
cancel = {{ translationKey: 'dialog.Cancel' }}
ok = {{ translationKey: 'dialog.Ok' }}
onSubmit = { this._onSubmit }
titleKey = 'dialog.displayNameRequired'
width = 'small'>
titleKey = 'dialog.displayNameRequired'>
<Input
autoFocus = { true }
label = { this.props.t('dialog.enterDisplayName') }

View File

@ -1,25 +1,21 @@
// @flow
import React from 'react';
import { WithTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { IState } from '../../app/types';
import CopyButton from '../../base/buttons/CopyButton';
import { getInviteURL } from '../../base/connection';
import { Dialog } from '../../base/dialog';
import { translate } from '../../base/i18n';
import { getInviteURL } from '../../base/connection/functions';
import { translate } from '../../base/i18n/functions';
import Dialog from '../../base/ui/components/web/Dialog';
type Props = {
interface Props extends WithTranslation {
/**
* Invoked to obtain translated strings.
*/
t: Function,
/**
* The URL of the conference.
*/
url: string
};
url: string;
}
/**
* Allow users to embed a jitsi meeting in an iframe.
@ -38,10 +34,9 @@ function EmbedMeeting({ t, url }: Props) {
return (
<Dialog
hideCancelButton = { true }
submitDisabled = { true }
titleKey = { 'embedMeeting.title' }
width = 'small'>
cancel = {{ hidden: true }}
ok = {{ hidden: true }}
titleKey = { 'embedMeeting.title' }>
<div className = 'embed-meeting-dialog'>
<textarea
aria-label = { t('dialog.embedMeeting') }
@ -60,7 +55,7 @@ function EmbedMeeting({ t, url }: Props) {
);
}
const mapStateToProps = state => {
const mapStateToProps = (state: IState) => {
return {
url: getInviteURL(state)
};

View File

@ -3,10 +3,7 @@ import { withStyles } from '@mui/styles';
import clsx from 'clsx';
import React, { Component } from 'react';
import { WithTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { IStore } from '../../../app/types';
import { hideDialog } from '../../../base/dialog/actions';
import { translate } from '../../../base/i18n/functions';
import Dialog from '../../../base/ui/components/web/Dialog';
@ -16,11 +13,6 @@ import Dialog from '../../../base/ui/components/web/Dialog';
*/
interface Props extends WithTranslation {
/**
* Dispatches close dialog.
*/
_onCloseDialog: () => void;
/**
* An object containing the CSS classes.
*/
@ -80,7 +72,8 @@ class KeyboardShortcutsDialog extends Component<Props> {
return (
<Dialog
onCancel = { this.props._onCloseDialog }
cancel = {{ hidden: true }}
ok = {{ hidden: true }}
titleKey = 'keyboardShortcuts.keyboardShortcuts'>
<div
id = 'keyboard-shortcuts'>
@ -130,16 +123,4 @@ class KeyboardShortcutsDialog extends Component<Props> {
}
}
/**
* Function that maps parts of Redux actions into component props.
*
* @param {Object} dispatch - Redux dispatch.
* @returns {Object}
*/
function mapDispatchToProps(dispatch: IStore['dispatch']) {
return {
_onCloseDialog: () => dispatch(hideDialog())
};
}
export default connect(null, mapDispatchToProps)(translate(withStyles(styles)(KeyboardShortcutsDialog)));
export default translate(withStyles(styles)(KeyboardShortcutsDialog));