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:
parent
21bcbdc12f
commit
f4f8808d95
|
@ -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';
|
||||
|
|
|
@ -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);
|
||||
});
|
|
@ -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:
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
};
|
|
@ -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));
|
|
@ -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') }
|
||||
|
|
|
@ -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)
|
||||
};
|
|
@ -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));
|
||||
|
|
Loading…
Reference in New Issue