mirror of https://github.com/jitsi/jitsi-meet
Co-authored-by: DimaG <dgeorgiev06@gmail.com>pull/3795/head jitsi-meet_3486
parent
82f714b608
commit
8a241ba2b7
Binary file not shown.
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
@ -0,0 +1,113 @@ |
||||
// @flow
|
||||
|
||||
import { Component } from 'react'; |
||||
|
||||
import { getLocalParticipant } from '../../base/participants'; |
||||
|
||||
import { sendMessage, toggleChat } from '../actions'; |
||||
|
||||
/** |
||||
* The type of the React {@code Component} props of {@code AbstractChat}. |
||||
*/ |
||||
export type Props = { |
||||
|
||||
/** |
||||
* True if the chat window should be rendered. |
||||
*/ |
||||
_isOpen: boolean, |
||||
|
||||
/** |
||||
* All the chat messages in the conference. |
||||
*/ |
||||
_messages: Array<Object>, |
||||
|
||||
/** |
||||
* Function to send a text message. |
||||
* |
||||
* @protected |
||||
*/ |
||||
_onSendMessage: Function, |
||||
|
||||
/** |
||||
* Function to toggle the chat window. |
||||
*/ |
||||
_onToggleChat: Function, |
||||
|
||||
/** |
||||
* Whether or not to block chat access with a nickname input form. |
||||
*/ |
||||
_showNamePrompt: boolean, |
||||
|
||||
/** |
||||
* The Redux dispatch function. |
||||
*/ |
||||
dispatch: Dispatch<*>, |
||||
|
||||
/** |
||||
* Function to be used to translate i18n labels. |
||||
*/ |
||||
t: Function |
||||
}; |
||||
|
||||
/** |
||||
* Implements an abstract chat panel. |
||||
*/ |
||||
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<*>) { |
||||
return { |
||||
/** |
||||
* Toggles the chat window. |
||||
* |
||||
* @returns {Function} |
||||
*/ |
||||
_onToggleChat() { |
||||
dispatch(toggleChat()); |
||||
}, |
||||
|
||||
/** |
||||
* Sends a text message. |
||||
* |
||||
* @private |
||||
* @param {string} text - The text message to be sent. |
||||
* @returns {void} |
||||
* @type {Function} |
||||
*/ |
||||
_onSendMessage(text: string) { |
||||
dispatch(sendMessage(text)); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* Maps (parts of) the redux state to {@link Chat} React {@code Component} |
||||
* props. |
||||
* |
||||
* @param {Object} state - The redux store/state. |
||||
* @private |
||||
* @returns {{ |
||||
* _isOpen: boolean, |
||||
* _messages: Array<Object>, |
||||
* _showNamePrompt: boolean |
||||
* }} |
||||
*/ |
||||
export function _mapStateToProps(state: Object) { |
||||
const { isOpen, messages } = state['features/chat']; |
||||
const _localParticipant = getLocalParticipant(state); |
||||
|
||||
return { |
||||
_isOpen: isOpen, |
||||
_messages: messages, |
||||
_showNamePrompt: !_localParticipant.name |
||||
}; |
||||
} |
@ -0,0 +1,48 @@ |
||||
// @flow
|
||||
|
||||
import { PureComponent } from 'react'; |
||||
|
||||
import { getAvatarURLByParticipantId } from '../../base/participants'; |
||||
|
||||
/** |
||||
* The type of the React {@code Component} props of {@code AbstractChatMessage}. |
||||
*/ |
||||
export type Props = { |
||||
|
||||
/** |
||||
* The URL of the avatar of the participant. |
||||
*/ |
||||
_avatarURL: string, |
||||
|
||||
/** |
||||
* The representation of a chat message. |
||||
*/ |
||||
message: Object, |
||||
|
||||
/** |
||||
* Invoked to receive translated strings. |
||||
*/ |
||||
t: Function |
||||
}; |
||||
|
||||
/** |
||||
* Abstract component to display a chat message. |
||||
*/ |
||||
export default class AbstractChatMessage<P: Props> extends PureComponent<P> {} |
||||
|
||||
/** |
||||
* Maps part of the Redux state to the props of this component. |
||||
* |
||||
* @param {Object} state - The Redux state. |
||||
* @param {Props} ownProps - The own props of the component. |
||||
* @returns {{ |
||||
* _avatarURL: string |
||||
* }} |
||||
*/ |
||||
export function _mapStateToProps(state: Object, ownProps: Props) { |
||||
const { message } = ownProps; |
||||
|
||||
return { |
||||
_avatarURL: getAvatarURLByParticipantId(state, message.user._id) |
||||
}; |
||||
} |
@ -1,2 +0,0 @@ |
||||
export Chat from './Chat'; |
||||
export ChatCounter from './ChatCounter'; |
@ -0,0 +1,3 @@ |
||||
// @flow
|
||||
|
||||
export * from './native'; |
@ -0,0 +1,3 @@ |
||||
// @flow
|
||||
|
||||
export * from './web'; |
@ -0,0 +1,168 @@ |
||||
// @flow
|
||||
|
||||
import React from 'react'; |
||||
import { SafeAreaView } from 'react-native'; |
||||
import { GiftedChat } from 'react-native-gifted-chat'; |
||||
import { connect } from 'react-redux'; |
||||
|
||||
import { translate } from '../../../base/i18n'; |
||||
import { BackButton, Header, HeaderLabel, Modal } from '../../../base/react'; |
||||
|
||||
import AbstractChat, { |
||||
_mapDispatchToProps, |
||||
_mapStateToProps as _abstractMapStateToProps, |
||||
type Props as AbstractProps |
||||
} from '../AbstractChat'; |
||||
|
||||
import ChatMessage from './ChatMessage'; |
||||
import styles from './styles'; |
||||
|
||||
type Props = AbstractProps & { |
||||
|
||||
/** |
||||
* True if the chat window should have a solid BG render. |
||||
*/ |
||||
_solidBackground: boolean |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Implements a React native component that renders the chat window (modal) of |
||||
* the mobile client. |
||||
*/ |
||||
class Chat extends AbstractChat<Props> { |
||||
|
||||
/** |
||||
* Initializes a new instance. |
||||
* |
||||
* @inheritdoc |
||||
*/ |
||||
constructor(props: Props) { |
||||
super(props); |
||||
|
||||
this._onSend = this._onSend.bind(this); |
||||
this._renderMessage = this._renderMessage.bind(this); |
||||
this._transformMessage = this._transformMessage.bind(this); |
||||
} |
||||
|
||||
/** |
||||
* Implements React's {@link Component#render()}. |
||||
* |
||||
* @inheritdoc |
||||
*/ |
||||
render() { |
||||
const messages = this.props._messages.map(this._transformMessage); |
||||
const modalStyle = [ |
||||
styles.modalBackdrop |
||||
]; |
||||
|
||||
if (this.props._solidBackground) { |
||||
// We only use a transparent background, when we are in a video
|
||||
// meeting to give a user a glympse of what's happening. Otherwise
|
||||
// we use a non-transparent background.
|
||||
modalStyle.push(styles.solidModalBackdrop); |
||||
} |
||||
|
||||
return ( |
||||
<Modal |
||||
onRequestClose = { this.props._onToggleChat } |
||||
visible = { this.props._isOpen }> |
||||
<Header> |
||||
<BackButton onPress = { this.props._onToggleChat } /> |
||||
<HeaderLabel labelKey = 'chat.title' /> |
||||
</Header> |
||||
<SafeAreaView style = { modalStyle }> |
||||
<GiftedChat |
||||
messages = { messages } |
||||
onSend = { this._onSend } |
||||
renderMessage = { this._renderMessage } /> |
||||
</SafeAreaView> |
||||
</Modal> |
||||
); |
||||
} |
||||
|
||||
_onSend: (Array<Object>) => void; |
||||
|
||||
/** |
||||
* Callback to trigger a message send action. |
||||
* |
||||
* @param {string} message - The chat message to display. |
||||
* @returns {void} |
||||
*/ |
||||
_onSend([ message ]) { |
||||
this.props._onSendMessage(message.text); |
||||
} |
||||
|
||||
_renderMessage: Object => React$Element<*> |
||||
|
||||
/** |
||||
* Renders a single message. |
||||
* |
||||
* @param {Object} messageProps - The message props object to be rendered. |
||||
* @returns {React$Element<*>} |
||||
*/ |
||||
_renderMessage(messageProps) { |
||||
const { currentMessage } = messageProps; |
||||
|
||||
return ( |
||||
<ChatMessage message = { currentMessage } /> |
||||
); |
||||
} |
||||
|
||||
_transformMessage: (Object, number) => Object; |
||||
|
||||
/** |
||||
* Transforms a Jitsi message object to a format that gifted-chat can |
||||
* handle. |
||||
* |
||||
* @param {Object} message - The chat message in our internal format. |
||||
* @param {number} index - The index of the message in the array. |
||||
* @returns {Object} |
||||
*/ |
||||
_transformMessage(message, index) { |
||||
const system = message.messageType === 'error'; |
||||
|
||||
return ( |
||||
{ |
||||
_id: index, |
||||
createdAt: new Date(message.timestamp), |
||||
messageType: message.messageType, |
||||
system, |
||||
text: system |
||||
? this.props.t('chat.error', { |
||||
error: message.error, |
||||
originalText: message.message |
||||
}) |
||||
: message.message, |
||||
user: { |
||||
_id: message.id, |
||||
name: message.displayName |
||||
} |
||||
} |
||||
); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Maps part of the Redux state to the props of this component. |
||||
* |
||||
* @param {Object} state - The Redux state. |
||||
* @returns {{ |
||||
* _solidBackground: boolean |
||||
* }} |
||||
*/ |
||||
function _mapStateToProps(state) { |
||||
const abstractReduxProps = _abstractMapStateToProps(state); |
||||
|
||||
return { |
||||
...abstractReduxProps, |
||||
|
||||
// Gifted chat requires the messages to be reverse ordered.
|
||||
_messages: [ |
||||
...abstractReduxProps._messages |
||||
].reverse(), |
||||
_solidBackground: state['features/base/conference'].audioOnly |
||||
}; |
||||
} |
||||
|
||||
export default translate(connect(_mapStateToProps, _mapDispatchToProps)(Chat)); |
@ -0,0 +1,129 @@ |
||||
// @flow
|
||||
|
||||
import { connect } from 'react-redux'; |
||||
|
||||
import { getLocalParticipant } from '../../../base/participants'; |
||||
import { |
||||
AbstractButton, |
||||
type AbstractButtonProps |
||||
} from '../../../base/toolbox'; |
||||
import { openDisplayNamePrompt } from '../../../display-name'; |
||||
|
||||
import { toggleChat } from '../../actions'; |
||||
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 |
||||
}; |
||||
|
||||
/** |
||||
* Implements an {@link AbstractButton} to open the chat screen on mobile. |
||||
*/ |
||||
class ChatButton extends AbstractButton<Props, *> { |
||||
accessibilityLabel = 'toolbar.accessibilityLabel.chat'; |
||||
iconName = 'chat'; |
||||
label = 'toolbar.chat'; |
||||
toggledIconName = 'chat-unread'; |
||||
|
||||
/** |
||||
* Handles clicking / pressing the button, and opens the appropriate dialog. |
||||
* |
||||
* @private |
||||
* @returns {void} |
||||
*/ |
||||
_handleClick() { |
||||
if (this.props._showNamePrompt) { |
||||
this.props._displayNameInputDialog(() => { |
||||
this.props._displayChat(); |
||||
}); |
||||
} else { |
||||
this.props._displayChat(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Renders the button toggled when there are unread messages. |
||||
* |
||||
* @protected |
||||
* @returns {boolean} |
||||
*/ |
||||
_isToggled() { |
||||
return Boolean(this.props._unreadMessageCount); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 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(toggleChat()); |
||||
}, |
||||
|
||||
/** |
||||
* Displays a diaply 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. |
||||
* |
||||
* @param {Object} state - The Redux state. |
||||
* @returns {{ |
||||
* _unreadMessageCount |
||||
* }} |
||||
*/ |
||||
function _mapStateToProps(state) { |
||||
const localParticipant = getLocalParticipant(state); |
||||
|
||||
return { |
||||
_showNamePrompt: !localParticipant.name, |
||||
_unreadMessageCount: getUnreadCount(state) |
||||
}; |
||||
} |
||||
|
||||
export default connect(_mapStateToProps, _mapDispatchToProps)(ChatButton); |
@ -0,0 +1,152 @@ |
||||
// @flow
|
||||
|
||||
import React from 'react'; |
||||
import { Text, View } from 'react-native'; |
||||
import { connect } from 'react-redux'; |
||||
|
||||
import { getLocalizedDateFormatter, translate } from '../../../base/i18n'; |
||||
import { Avatar } from '../../../base/participants'; |
||||
|
||||
import AbstractChatMessage, { |
||||
_mapStateToProps as _abstractMapStateToProps, |
||||
type Props as AbstractProps |
||||
} from '../AbstractChatMessage'; |
||||
import styles from './styles'; |
||||
|
||||
/** |
||||
* Size of the rendered avatar in the message. |
||||
*/ |
||||
const AVATAR_SIZE = 32; |
||||
|
||||
/** |
||||
* Formatter string to display the message timestamp. |
||||
*/ |
||||
const TIMESTAMP_FORMAT = 'H:mm'; |
||||
|
||||
type Props = AbstractProps & { |
||||
|
||||
/** |
||||
* True if the chat window has a solid BG so then we have to adopt in style. |
||||
*/ |
||||
_solidBackground: boolean |
||||
} |
||||
|
||||
/** |
||||
* Renders a single chat message. |
||||
*/ |
||||
class ChatMessage extends AbstractChatMessage<Props> { |
||||
/** |
||||
* Implements {@code Component#render}. |
||||
* |
||||
* @inheritdoc |
||||
*/ |
||||
render() { |
||||
const { message } = this.props; |
||||
const timeStamp = getLocalizedDateFormatter( |
||||
message.createdAt).format(TIMESTAMP_FORMAT); |
||||
const localMessage = message.messageType === 'local'; |
||||
|
||||
// Style arrays that need to be updated in various scenarios, such as
|
||||
// error messages or others.
|
||||
const detailsWrapperStyle = [ |
||||
styles.detailsWrapper |
||||
]; |
||||
const textWrapperStyle = [ |
||||
styles.textWrapper |
||||
]; |
||||
const timeTextStyles = [ |
||||
styles.timeText |
||||
]; |
||||
|
||||
if (localMessage) { |
||||
// The wrapper needs to be aligned to the right.
|
||||
detailsWrapperStyle.push(styles.ownMessageDetailsWrapper); |
||||
|
||||
// The bubble needs to be differently styled.
|
||||
textWrapperStyle.push(styles.ownTextWrapper); |
||||
} else if (message.system) { |
||||
// The bubble needs to be differently styled.
|
||||
textWrapperStyle.push(styles.systemTextWrapper); |
||||
} |
||||
|
||||
if (this.props._solidBackground) { |
||||
timeTextStyles.push(styles.solidBGTimeText); |
||||
} |
||||
|
||||
return ( |
||||
<View style = { styles.messageWrapper } > |
||||
{ |
||||
|
||||
// Avatar is only rendered for remote messages.
|
||||
!localMessage && this._renderAvatar() |
||||
} |
||||
<View style = { detailsWrapperStyle }> |
||||
<View style = { textWrapperStyle } > |
||||
{ |
||||
|
||||
// Display name is only rendered for remote
|
||||
// messages.
|
||||
!localMessage && this._renderDisplayName() |
||||
} |
||||
<Text style = { styles.messageText }> |
||||
{ message.text } |
||||
</Text> |
||||
</View> |
||||
<Text style = { timeTextStyles }> |
||||
{ timeStamp } |
||||
</Text> |
||||
</View> |
||||
</View> |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Renders the avatar of the sender. |
||||
* |
||||
* @returns {React$Element<*>} |
||||
*/ |
||||
_renderAvatar() { |
||||
const { _avatarURL } = this.props; |
||||
|
||||
return ( |
||||
<View style = { styles.avatarWrapper }> |
||||
<Avatar |
||||
size = { AVATAR_SIZE } |
||||
uri = { _avatarURL } /> |
||||
</View> |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Renders the display name of the sender. |
||||
* |
||||
* @returns {React$Element<*>} |
||||
*/ |
||||
_renderDisplayName() { |
||||
const { message } = this.props; |
||||
|
||||
return ( |
||||
<Text style = { styles.displayName }> |
||||
{ message.user.name } |
||||
</Text> |
||||
); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Maps part of the Redux state to the props of this component. |
||||
* |
||||
* @param {Object} state - The Redux state. |
||||
* @param {Props} ownProps - The own props of the component. |
||||
* @returns {{ |
||||
* _solidBackground: boolean |
||||
* }} |
||||
*/ |
||||
function _mapStateToProps(state, ownProps) { |
||||
return { |
||||
..._abstractMapStateToProps(state, ownProps), |
||||
_solidBackground: state['features/base/conference'].audioOnly |
||||
}; |
||||
} |
||||
|
||||
export default translate(connect(_mapStateToProps)(ChatMessage)); |
@ -0,0 +1,4 @@ |
||||
// @flow
|
||||
|
||||
export { default as Chat } from './Chat'; |
||||
export { default as ChatButton } from './ChatButton'; |
@ -0,0 +1,124 @@ |
||||
// @flow
|
||||
|
||||
import { |
||||
ColorPalette, |
||||
createStyleSheet |
||||
} from '../../../base/styles'; |
||||
|
||||
/** |
||||
* The styles of the feature chat. |
||||
* |
||||
* NOTE: Sizes and colors come from the 8x8 guidelines. This is the first |
||||
* component to receive this treating, if others happen to have similar, we |
||||
* need to extract the brand colors and sizes into a branding feature (planned |
||||
* for the future). |
||||
*/ |
||||
export default createStyleSheet({ |
||||
|
||||
/** |
||||
* Wrapper View for the avatar. |
||||
*/ |
||||
avatarWrapper: { |
||||
marginRight: 8 |
||||
}, |
||||
|
||||
/** |
||||
* Wrapper for the details together, such as name, message and time. |
||||
*/ |
||||
detailsWrapper: { |
||||
alignItems: 'flex-start', |
||||
flex: 1, |
||||
flexDirection: 'column' |
||||
}, |
||||
|
||||
/** |
||||
* The text node for the display name. |
||||
*/ |
||||
displayName: { |
||||
color: 'rgb(118, 136, 152)', |
||||
fontSize: 13 |
||||
}, |
||||
|
||||
/** |
||||
* The message text itself. |
||||
*/ |
||||
messageText: { |
||||
color: 'rgb(28, 32, 37)', |
||||
fontSize: 15 |
||||
}, |
||||
|
||||
/** |
||||
* Wrapper View for the entire block. |
||||
*/ |
||||
messageWrapper: { |
||||
alignItems: 'flex-start', |
||||
flex: 1, |
||||
flexDirection: 'row', |
||||
marginHorizontal: 17, |
||||
marginVertical: 4 |
||||
}, |
||||
|
||||
/** |
||||
* Background of the chat screen. Currently it's set to a transparent value |
||||
* as the idea is that the participant would still want to see at least a |
||||
* part of the video when he/she is in the chat window. |
||||
*/ |
||||
modalBackdrop: { |
||||
backgroundColor: 'rgba(127, 127, 127, 0.8)', |
||||
flex: 1 |
||||
}, |
||||
|
||||
/** |
||||
* Style modifier for the {@code detailsWrapper} for own messages. |
||||
*/ |
||||
ownMessageDetailsWrapper: { |
||||
alignItems: 'flex-end' |
||||
}, |
||||
|
||||
/** |
||||
* Style modifier for the {@code textWrapper} for own messages. |
||||
*/ |
||||
ownTextWrapper: { |
||||
backgroundColor: 'rgb(210, 231, 249)', |
||||
borderTopLeftRadius: 8, |
||||
borderTopRightRadius: 0 |
||||
}, |
||||
|
||||
solidBGTimeText: { |
||||
color: 'rgb(164, 184, 209)' |
||||
}, |
||||
|
||||
/** |
||||
* Style modifier for the chat window when we're in audio only mode. |
||||
*/ |
||||
solidModalBackdrop: { |
||||
backgroundColor: ColorPalette.white |
||||
}, |
||||
|
||||
/** |
||||
* Style modifier for system (error) messages. |
||||
*/ |
||||
systemTextWrapper: { |
||||
backgroundColor: 'rgb(247, 215, 215)' |
||||
}, |
||||
|
||||
/** |
||||
* Wrapper for the name and the message text. |
||||
*/ |
||||
textWrapper: { |
||||
alignItems: 'flex-start', |
||||
backgroundColor: 'rgb(240, 243, 247)', |
||||
borderRadius: 8, |
||||
borderTopLeftRadius: 0, |
||||
flexDirection: 'column', |
||||
padding: 9 |
||||
}, |
||||
|
||||
/** |
||||
* Text node for the timestamp. |
||||
*/ |
||||
timeText: { |
||||
color: ColorPalette.white, |
||||
fontSize: 13 |
||||
} |
||||
}); |
@ -1,34 +1,20 @@ |
||||
// @flow
|
||||
|
||||
import React, { PureComponent } from 'react'; |
||||
import React from 'react'; |
||||
import { toArray } from 'react-emoji-render'; |
||||
import Linkify from 'react-linkify'; |
||||
|
||||
|
||||
import { translate } from '../../base/i18n'; |
||||
import { translate } from '../../../base/i18n'; |
||||
|
||||
/** |
||||
* The type of the React {@code Component} props of {@link Chat}. |
||||
*/ |
||||
type Props = { |
||||
|
||||
/** |
||||
* The redux representation of a chat message. |
||||
*/ |
||||
message: Object, |
||||
|
||||
/** |
||||
* Invoked to receive translated strings. |
||||
*/ |
||||
t: Function |
||||
}; |
||||
import AbstractChatMessage, { |
||||
type Props |
||||
} from '../AbstractChatMessage'; |
||||
|
||||
/** |
||||
* Displays as passed in chat message. |
||||
* |
||||
* @extends Component |
||||
* Renders a single chat message. |
||||
*/ |
||||
class ChatMessage extends PureComponent<Props> { |
||||
class ChatMessage extends AbstractChatMessage<Props> { |
||||
/** |
||||
* Implements React's {@link Component#render()}. |
||||
* |
@ -0,0 +1,4 @@ |
||||
// @flow
|
||||
|
||||
export { default as Chat } from './Chat'; |
||||
export { default as ChatCounter } from './ChatCounter'; |
@ -0,0 +1,30 @@ |
||||
// @flow
|
||||
|
||||
import React from 'react'; |
||||
import { connect } from 'react-redux'; |
||||
|
||||
import { InputDialog } from '../../base/dialog'; |
||||
|
||||
import AbstractDisplayNamePrompt from './AbstractDisplayNamePrompt'; |
||||
|
||||
/** |
||||
* Implements a component to render a display name prompt. |
||||
*/ |
||||
class DisplayNamePrompt extends AbstractDisplayNamePrompt<*> { |
||||
/** |
||||
* Implements React's {@link Component#render()}. |
||||
* |
||||
* @inheritdoc |
||||
*/ |
||||
render() { |
||||
return ( |
||||
<InputDialog |
||||
contentKey = 'dialog.enterDisplayName' |
||||
onSubmit = { this._onSetDisplayName } /> |
||||
); |
||||
} |
||||
|
||||
_onSetDisplayName: string => boolean; |
||||
} |
||||
|
||||
export default connect()(DisplayNamePrompt); |
Loading…
Reference in new issue