feat refactors the chat flow so it has open and close functions

This commit is contained in:
tmoldovan8x8 2021-02-12 13:18:16 +02:00 committed by GitHub
parent 2cd43ba2e4
commit 65c56669c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 199 additions and 208 deletions

View File

@ -16,6 +16,7 @@ import { parseJWTFromURLParams } from '../../react/features/base/jwt';
import JitsiMeetJS, { JitsiRecordingConstants } from '../../react/features/base/lib-jitsi-meet';
import { pinParticipant, getParticipantById, kickParticipant } from '../../react/features/base/participants';
import { setPrivateMessageRecipient } from '../../react/features/chat/actions';
import { openChat } from '../../react/features/chat/actions.web';
import {
processExternalDeviceRequest
} from '../../react/features/device-selection/functions';
@ -342,7 +343,7 @@ function initCommands() {
if (!isChatOpen) {
APP.UI.toggleChat();
}
APP.store.dispatch(setPrivateMessageRecipient(participant));
APP.store.dispatch(openChat(participant));
} else {
logger.error('No participant found for the given participantId');
}

View File

@ -24,6 +24,24 @@ export const ADD_MESSAGE = 'ADD_MESSAGE';
*/
export const CLEAR_MESSAGES = 'CLEAR_MESSAGES';
/**
* The type of the action which signals the cancelation the chat panel.
*
* {
* type: CLOSE_CHAT
* }
*/
export const CLOSE_CHAT = 'CLOSE_CHAT';
/**
* The type of the action which signals to display the chat panel.
*
* {
* type: OPEN_CHAT
* }
*/
export const OPEN_CHAT = 'OPEN_CHAT';
/**
* The type of the action which signals a send a chat message to everyone in the
* conference.
@ -46,12 +64,3 @@ export const SEND_MESSAGE = 'SEND_MESSAGE';
* }
*/
export const SET_PRIVATE_MESSAGE_RECIPIENT = 'SET_PRIVATE_MESSAGE_RECIPIENT';
/**
* The type of the action which signals to toggle the display of the chat panel.
*
* {
* type: TOGGLE_CHAT
* }
*/
export const TOGGLE_CHAT = 'TOGGLE_CHAT';

View File

@ -3,6 +3,7 @@
import {
ADD_MESSAGE,
CLEAR_MESSAGES,
CLOSE_CHAT,
SEND_MESSAGE,
SET_PRIVATE_MESSAGE_RECIPIENT
} from './actionTypes';
@ -49,6 +50,19 @@ export function clearMessages() {
};
}
/**
* Action to signal the closing of the chat dialog.
*
* @returns {{
* type: CLOSE_CHAT
* }}
*/
export function closeChat() {
return {
type: CLOSE_CHAT
};
}
/**
* Sends a chat message to everyone in the conference.
*

View File

@ -1,16 +1,22 @@
// @flow
import { TOGGLE_CHAT } from './actionTypes';
import { OPEN_CHAT } from './actionTypes';
export * from './actions.any';
/**
* Toggles display of the chat panel.
* Displays the chat panel.
*
* @returns {Function}
* @param {Object} participant - The recipient for the private chat.
*
* @returns {{
* participant: Participant,
* type: OPEN_CHAT
* }}
*/
export function toggleChat() {
return function(dispatch: (Object) => Object) {
dispatch({ type: TOGGLE_CHAT });
export function openChat(participant: Object) {
return {
participant,
type: OPEN_CHAT
};
}

View File

@ -1,20 +1,44 @@
// @flow
import type { Dispatch } from 'redux';
import VideoLayout from '../../../modules/UI/videolayout/VideoLayout';
import { TOGGLE_CHAT } from './actionTypes';
import { OPEN_CHAT } from './actionTypes';
import { closeChat } from './actions.any';
export * from './actions.any';
/**
* Toggles display of the chat side panel while also taking window
* resize into account.
* Displays the chat panel.
*
* @param {Object} participant - The recipient for the private chat.
* @returns {{
* participant: Participant,
* type: OPEN_CHAT
* }}
*/
export function openChat(participant: Object) {
return function(dispatch: (Object) => Object) {
dispatch({ participant,
type: OPEN_CHAT });
VideoLayout.onResize();
};
}
/**
* Toggles display of the chat panel.
*
* @returns {Function}
*/
export function toggleChat() {
return function(dispatch: (Object) => Object) {
dispatch({ type: TOGGLE_CHAT });
VideoLayout.onResize();
return (dispatch: Dispatch<any>, getState: Function) => {
const isOpen = getState()['features/chat'].isOpen;
if (isOpen) {
dispatch(closeChat());
} else {
dispatch(openChat());
}
};
}

View File

@ -5,7 +5,7 @@ import type { Dispatch } from 'redux';
import { isMobileBrowser } from '../../base/environment/utils';
import { getLocalParticipant } from '../../base/participants';
import { sendMessage, toggleChat } from '../actions';
import { sendMessage } from '../actions';
import { DESKTOP_SMALL_WIDTH_THRESHOLD, MOBILE_SMALL_WIDTH_THRESHOLD } from '../constants';
/**
@ -59,41 +59,34 @@ export type Props = {
/**
* Implements an abstract chat panel.
*/
export default class AbstractChat<P: Props> extends Component<P> {}
export default class AbstractChat<P: Props> extends Component<P> {
/**
* Maps redux actions to the props of the component.
*
* @param {Function} dispatch - The redux action {@code dispatch} function.
* @returns {{
* _onSendMessage: Function,
* _onToggleChat: Function
* }}
* @private
*/
export function _mapDispatchToProps(dispatch: Dispatch<any>) {
return {
/**
* Toggles the chat window.
*
* @returns {Function}
*/
_onToggleChat() {
dispatch(toggleChat());
},
/**
* Initializes a new {@code AbstractChat} instance.
*
* @param {Props} props - The React {@code Component} props to initialize
* the new {@code AbstractChat} instance with.
*/
constructor(props: P) {
super(props);
/**
* Sends a text message.
*
* @private
* @param {string} text - The text message to be sent.
* @returns {void}
* @type {Function}
*/
_onSendMessage(text: string) {
dispatch(sendMessage(text));
}
};
// Bind event handlers so they are only bound once per instance.
this._onSendMessage = this._onSendMessage.bind(this);
}
_onSendMessage: (string) => void;
/**
* Sends a text message.
*
* @private
* @param {string} text - The text message to be sent.
* @returns {void}
* @type {Function}
*/
_onSendMessage(text: string) {
this.props.dispatch(sendMessage(text));
}
}
/**

View File

@ -5,7 +5,7 @@ import { IconMessage, IconReply } from '../../base/icons';
import { getParticipantById } from '../../base/participants';
import { connect } from '../../base/redux';
import { AbstractButton, type AbstractButtonProps } from '../../base/toolbox/components';
import { setPrivateMessageRecipient } from '../actions';
import { openChat } from '../actions';
export type Props = AbstractButtonProps & {
@ -24,15 +24,15 @@ export type Props = AbstractButtonProps & {
*/
t: Function,
/**
* The Redux dispatch function.
*/
dispatch: Function,
/**
* The participant object retreived from Redux.
*/
_participant: Object,
/**
* Function to dispatch the result of the participant selection to send a private message.
*/
_setPrivateMessageRecipient: Function
};
/**
@ -51,9 +51,9 @@ class PrivateMessageButton extends AbstractButton<Props, any> {
* @returns {void}
*/
_handleClick() {
const { _participant, _setPrivateMessageRecipient } = this.props;
const { dispatch, _participant } = this.props;
_setPrivateMessageRecipient(_participant);
dispatch(openChat(_participant));
}
/**
@ -69,20 +69,6 @@ class PrivateMessageButton extends AbstractButton<Props, any> {
}
/**
* Maps part of the props of this component to Redux actions.
*
* @param {Function} dispatch - The Redux dispatch function.
* @returns {Props}
*/
export function _mapDispatchToProps(dispatch: Function): $Shape<Props> {
return {
_setPrivateMessageRecipient: participant => {
dispatch(setPrivateMessageRecipient(participant));
}
};
}
/**
* Maps part of the Redux store to the props of this component.
*
@ -96,4 +82,4 @@ export function _mapStateToProps(state: Object, ownProps: Props): $Shape<Props>
};
}
export default translate(connect(_mapStateToProps, _mapDispatchToProps)(PrivateMessageButton));
export default translate(connect(_mapStateToProps)(PrivateMessageButton));

View File

@ -5,9 +5,9 @@ import React from 'react';
import { translate } from '../../../base/i18n';
import { JitsiModal } from '../../../base/modal';
import { connect } from '../../../base/redux';
import { closeChat } from '../../actions.any';
import { CHAT_VIEW_MODAL_ID } from '../../constants';
import AbstractChat, {
_mapDispatchToProps,
_mapStateToProps,
type Props
} from '../AbstractChat';
@ -48,11 +48,13 @@ class Chat extends AbstractChat<Props> {
<MessageContainer messages = { this.props._messages } />
<MessageRecipient />
<ChatInputBar onSend = { this.props._onSendMessage } />
<ChatInputBar onSend = { this._onSendMessage } />
</JitsiModal>
);
}
_onSendMessage: (string) => void;
_onClose: () => boolean
/**
@ -61,10 +63,10 @@ class Chat extends AbstractChat<Props> {
* @returns {boolean}
*/
_onClose() {
this.props._onToggleChat();
this.props.dispatch(closeChat());
return true;
}
}
export default translate(connect(_mapStateToProps, _mapDispatchToProps)(Chat));
export default translate(connect(_mapStateToProps)(Chat));

View File

@ -2,41 +2,22 @@
import { CHAT_ENABLED, getFeatureFlag } from '../../../base/flags';
import { IconChat, IconChatUnread } from '../../../base/icons';
import { setActiveModalId } from '../../../base/modal';
import { getLocalParticipant } from '../../../base/participants';
import { connect } from '../../../base/redux';
import {
AbstractButton,
type AbstractButtonProps
} from '../../../base/toolbox/components';
import { openDisplayNamePrompt } from '../../../display-name';
import { CHAT_VIEW_MODAL_ID } from '../../constants';
import { openChat } from '../../actions.native';
import { getUnreadCount } from '../../functions';
type Props = AbstractButtonProps & {
/**
* Function to display chat.
*
* @protected
*/
_displayChat: Function,
/**
* Function to diaply the name prompt before displaying the chat
* window, if the user has no display name set.
*/
_displayNameInputDialog: Function,
/**
* Whether or not to block chat access with a nickname input form.
*/
_showNamePrompt: boolean,
/**
* The unread message count.
*/
_unreadMessageCount: number
_unreadMessageCount: number,
dispatch: Function
};
/**
@ -55,13 +36,7 @@ class ChatButton extends AbstractButton<Props, *> {
* @returns {void}
*/
_handleClick() {
if (this.props._showNamePrompt) {
this.props._displayNameInputDialog(() => {
this.props._displayChat();
});
} else {
this.props._displayChat();
}
this.props.dispatch(openChat());
}
/**
@ -75,41 +50,6 @@ class ChatButton extends AbstractButton<Props, *> {
}
}
/**
* Maps redux actions to the props of the component.
*
* @param {Function} dispatch - The redux action {@code dispatch} function.
* @returns {{
* _displayChat,
* _displayNameInputDialog
* }}
* @private
*/
function _mapDispatchToProps(dispatch: Function) {
return {
/**
* Launches native invite dialog.
*
* @private
* @returns {void}
*/
_displayChat() {
dispatch(setActiveModalId(CHAT_VIEW_MODAL_ID));
},
/**
* Displays a display name prompt.
*
* @param {Function} onPostSubmit - The function to invoke after a
* succesfulsetting of the display name.
* @returns {void}
*/
_displayNameInputDialog(onPostSubmit) {
dispatch(openDisplayNamePrompt(onPostSubmit));
}
};
}
/**
* Maps part of the redux state to the component's props.
*
@ -118,15 +58,13 @@ function _mapDispatchToProps(dispatch: Function) {
* @returns {Props}
*/
function _mapStateToProps(state, ownProps) {
const localParticipant = getLocalParticipant(state);
const enabled = getFeatureFlag(state, CHAT_ENABLED, true);
const { visible = enabled } = ownProps;
return {
_showNamePrompt: !localParticipant.name,
_unreadMessageCount: getUnreadCount(state),
visible
};
}
export default connect(_mapStateToProps, _mapDispatchToProps)(ChatButton);
export default connect(_mapStateToProps)(ChatButton);

View File

@ -4,8 +4,8 @@ import React from 'react';
import { translate } from '../../../base/i18n';
import { connect } from '../../../base/redux';
import { toggleChat } from '../../actions.web';
import AbstractChat, {
_mapDispatchToProps,
_mapStateToProps,
type Props
} from '../AbstractChat';
@ -49,9 +49,8 @@ class Chat extends AbstractChat<Props> {
// Bind event handlers so they are only bound once for every instance.
this._renderPanelContent = this._renderPanelContent.bind(this);
// Bind event handlers so they are only bound once for every instance.
this._onChatInputResize = this._onChatInputResize.bind(this);
this._onToggleChat = this._onToggleChat.bind(this);
}
/**
@ -119,7 +118,7 @@ class Chat extends AbstractChat<Props> {
<MessageRecipient />
<ChatInput
onResize = { this._onChatInputResize }
onSend = { this.props._onSendMessage } />
onSend = { this._onSendMessage } />
</>
);
}
@ -135,7 +134,7 @@ class Chat extends AbstractChat<Props> {
return (
<Header
className = 'chat-header'
onCancel = { this.props._onToggleChat } />
onCancel = { this._onToggleChat } />
);
}
@ -197,6 +196,20 @@ class Chat extends AbstractChat<Props> {
this._messageContainerRef.current.scrollToBottom(withAnimation);
}
}
_onSendMessage: (string) => void;
_onToggleChat: () => void;
/**
* Toggles the chat window.
*
* @returns {Function}
*/
_onToggleChat() {
this.props.dispatch(toggleChat());
}
}
export default translate(connect(_mapStateToProps, _mapDispatchToProps)(Chat));
export default translate(connect(_mapStateToProps)(Chat));

View File

@ -5,7 +5,7 @@ import React from 'react';
import { translate } from '../../../base/i18n';
import { Icon, IconClose } from '../../../base/icons';
import { connect } from '../../../base/redux';
import { toggleChat } from '../../../chat';
import { closeChat } from '../../actions.any';
type Props = {
@ -42,6 +42,6 @@ function Header({ onCancel, className, t }: Props) {
);
}
const mapDispatchToProps = { onCancel: toggleChat };
const mapDispatchToProps = { onCancel: closeChat };
export default translate(connect(null, mapDispatchToProps)(Header));

View File

@ -18,10 +18,12 @@ import {
} from '../base/participants';
import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
import { playSound, registerSound, unregisterSound } from '../base/sounds';
import { openDisplayNamePrompt } from '../display-name';
import { showToolbox } from '../toolbox/actions';
import { ADD_MESSAGE, TOGGLE_CHAT, SEND_MESSAGE, SET_PRIVATE_MESSAGE_RECIPIENT } from './actionTypes';
import { addMessage, clearMessages, toggleChat } from './actions';
import { ADD_MESSAGE, SEND_MESSAGE, OPEN_CHAT, CLOSE_CHAT } from './actionTypes';
import { addMessage, clearMessages } from './actions';
import { closeChat } from './actions.any';
import { ChatPrivacyDialog } from './components';
import {
CHAT_VIEW_MODAL_ID,
@ -52,6 +54,7 @@ const PRIVACY_NOTICE_TIMEOUT = 20 * 1000;
*/
MiddlewareRegistry.register(store => next => action => {
const { dispatch, getState } = store;
const localParticipant = getLocalParticipant(getState());
let isOpen, unreadCount;
switch (action.type) {
@ -63,14 +66,7 @@ MiddlewareRegistry.register(store => next => action => {
APP.API.notifyChatUpdated(unreadCount, isOpen);
}
break;
case TOGGLE_CHAT:
unreadCount = 0;
isOpen = !getState()['features/chat'].isOpen;
if (typeof APP !== 'undefined') {
APP.API.notifyChatUpdated(unreadCount, isOpen);
}
break;
case APP_WILL_MOUNT:
dispatch(
registerSound(INCOMING_MSG_SOUND_ID, INCOMING_MSG_SOUND_FILE));
@ -84,6 +80,32 @@ MiddlewareRegistry.register(store => next => action => {
_addChatMsgListener(action.conference, store);
break;
case OPEN_CHAT:
if (localParticipant.name) {
dispatch(setActiveModalId(CHAT_VIEW_MODAL_ID));
_maybeFocusField();
} else {
dispatch(openDisplayNamePrompt(() => {
dispatch(setActiveModalId(CHAT_VIEW_MODAL_ID));
_maybeFocusField();
}));
}
unreadCount = 0;
if (typeof APP !== 'undefined') {
APP.API.notifyChatUpdated(unreadCount, true);
}
break;
case CLOSE_CHAT:
unreadCount = 0;
if (typeof APP !== 'undefined') {
APP.API.notifyChatUpdated(unreadCount, true);
}
break;
case SEND_MESSAGE: {
const state = store.getState();
const { conference } = state['features/base/conference'];
@ -117,12 +139,6 @@ MiddlewareRegistry.register(store => next => action => {
}
break;
}
case SET_PRIVATE_MESSAGE_RECIPIENT: {
Boolean(action.participant) && dispatch(setActiveModalId(CHAT_VIEW_MODAL_ID));
_maybeFocusField();
break;
}
}
return next(action);
@ -141,7 +157,7 @@ StateListenerRegistry.register(
if (getState()['features/chat'].isOpen) {
// Closes the chat if it's left open.
dispatch(toggleChat());
dispatch(closeChat());
}
// Clear chat messages.

View File

@ -1,15 +1,14 @@
// @flow
import { SET_ACTIVE_MODAL_ID } from '../base/modal';
import { ReducerRegistry } from '../base/redux';
import {
ADD_MESSAGE,
CLEAR_MESSAGES,
SET_PRIVATE_MESSAGE_RECIPIENT,
TOGGLE_CHAT
CLOSE_CHAT,
OPEN_CHAT,
SET_PRIVATE_MESSAGE_RECIPIENT
} from './actionTypes';
import { CHAT_VIEW_MODAL_ID } from './constants';
const DEFAULT_STATE = {
isOpen: false,
@ -58,38 +57,28 @@ ReducerRegistry.register('features/chat', (state = DEFAULT_STATE, action) => {
messages: []
};
case SET_ACTIVE_MODAL_ID:
if (action.activeModalId === CHAT_VIEW_MODAL_ID) {
return updateChatState(state);
}
break;
case SET_PRIVATE_MESSAGE_RECIPIENT:
return {
...state,
isOpen: Boolean(action.participant) || state.isOpen,
privateMessageRecipient: action.participant
};
case TOGGLE_CHAT:
return updateChatState(state);
case OPEN_CHAT:
return {
...state,
isOpen: true,
privateMessageRecipient: action.participant
};
case CLOSE_CHAT:
return {
...state,
isOpen: false,
lastReadMessage: state.messages[
navigator.product === 'ReactNative' ? 0 : state.messages.length - 1],
privateMessageRecipient: action.participant
};
}
return state;
});
/**
* Updates the chat status on opening the chat view.
*
* @param {Object} state - The Redux state of the feature.
* @returns {Object}
*/
function updateChatState(state) {
return {
...state,
isOpen: !state.isOpen,
lastReadMessage: state.messages[
navigator.product === 'ReactNative' ? 0 : state.messages.length - 1],
privateMessageRecipient: state.isOpen ? undefined : state.privateMessageRecipient
};
}

View File

@ -5,8 +5,8 @@ import React, { Component } from 'react';
import { translate } from '../../../base/i18n';
import { IconMessage } from '../../../base/icons';
import { connect } from '../../../base/redux';
import { openChat } from '../../../chat/';
import {
_mapDispatchToProps,
_mapStateToProps as _abstractMapStateToProps,
type Props as AbstractProps
} from '../../../chat/components/PrivateMessageButton';
@ -72,9 +72,9 @@ class PrivateMessageMenuButton extends Component<Props> {
* @returns {void}
*/
_onClick() {
const { _participant, _setPrivateMessageRecipient } = this.props;
const { dispatch, _participant } = this.props;
_setPrivateMessageRecipient(_participant);
dispatch(openChat(_participant));
}
}
@ -93,4 +93,4 @@ function _mapStateToProps(state: Object, ownProps: Props): $Shape<Props> {
};
}
export default translate(connect(_mapStateToProps, _mapDispatchToProps)(PrivateMessageMenuButton));
export default translate(connect(_mapStateToProps)(PrivateMessageMenuButton));