From faf59e56393ee939a27bc225f872037143878124 Mon Sep 17 00:00:00 2001 From: George James Date: Sun, 5 Jul 2020 11:35:41 +0400 Subject: [PATCH 1/6] Chat preview framework --- css/_chat-preview.scss | 0 css/main.scss | 8 +- .../chat/components/AbstractChatPreview.js | 176 ++++++++++++++++++ .../chat/components/web/ChatPreview.js | 30 +++ react/features/chat/components/web/index.js | 1 + .../conference/components/web/Conference.js | 6 +- .../welcome/components/WelcomePage.web.js | 27 +-- 7 files changed, 228 insertions(+), 20 deletions(-) create mode 100644 css/_chat-preview.scss create mode 100644 react/features/chat/components/AbstractChatPreview.js create mode 100644 react/features/chat/components/web/ChatPreview.js diff --git a/css/_chat-preview.scss b/css/_chat-preview.scss new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/css/main.scss b/css/main.scss index d51b1435a9e76..6a21aa936d360 100755 --- a/css/main.scss +++ b/css/main.scss @@ -90,14 +90,14 @@ $flagsImagePath: "../images/"; @import 'chrome-extension-banner'; @import 'settings-button'; @import 'icons'; -@import 'meter'; -@import 'audio-preview'; -@import 'video-preview'; +// @import 'meter'; +// @import 'audio-preview'; +// @import 'video-preview'; @import 'prejoin'; @import 'prejoin-dialog'; @import 'country-picker'; @import 'modals/invite/invite_more'; @import 'modals/security/security'; @import 'premeeting-screens'; - +@import 'chat-preview' /* Modules END */ diff --git a/react/features/chat/components/AbstractChatPreview.js b/react/features/chat/components/AbstractChatPreview.js new file mode 100644 index 0000000000000..5cfc792a02e3e --- /dev/null +++ b/react/features/chat/components/AbstractChatPreview.js @@ -0,0 +1,176 @@ +// @flow + +import { Component } from 'react'; +import type { Dispatch } from 'redux'; + +// import { getLocalParticipant } from '../../base/participants'; +import { sendMessage, toggleChat, setPrivateMessageRecipient, markAsRead, markPublicAsRead } from '../actions'; + +// import { getUnreadSinceLastRead } from '../functions'; + +/** + * The type of the React {@code Component} props of {@code AbstractChatPreview}. + */ +export type Props = { + + /** + * True if the chat window should be rendered. + */ + _isOpen: boolean, + + /** + * All the chat messages in the conference. + */ + _messages: Array, + + /** + * All the participants in the conference. + */ + _participants: Array, + + /** + * 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, + + /** + * Function to be used to set private message recipient. + */ + _setPrivateMessageRecipient: Function, + + /** + * Private message recipient. + */ + _privateMessageRecipient: Object, + + /** + * The local participant. + */ + _localParticipant: Object, + + /** + * Messages since last read . + */ + _messagesSinceLastRead: Array, + + /** + * Mark message as read + */ + _markAsRead: Function, + + /** + * Mark non-private message as read + */ + _markPublicAsRead: Function, +}; + +/** + * Implements an abstract chat panel. + */ +export default class AbstractChatPreview extends Component {} + +/** + * 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)); + }, + + _setPrivateMessageRecipient(participant: Object) { + dispatch(setPrivateMessageRecipient(participant)); + }, + + _markAsRead(localParticipant: Object, participant: Object) { + dispatch(markAsRead(localParticipant, participant)); + }, + + _markPublicAsRead() { + dispatch(markPublicAsRead()); + } + }; +} + +/** + * 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, + * _showNamePrompt: boolean + * }} + */ +export function _mapStateToProps(state: Object) { + const { + // isOpen, + messages + + // privateMessageRecipient + } = state['features/chat']; + + // const participants = state['features/base/participants']; + // const _localParticipant = getLocalParticipant(state); + + // const _messagesSinceLastRead = getUnreadSinceLastRead(state); + + return { + // _isOpen: isOpen, + _messages: messages + + // _participants: participants, + // _showNamePrompt: !_localParticipant.name, + // _localParticipant, + // _messagesSinceLastRead, + // _privateMessageRecipient: privateMessageRecipient + }; +} diff --git a/react/features/chat/components/web/ChatPreview.js b/react/features/chat/components/web/ChatPreview.js new file mode 100644 index 0000000000000..ab4b276ea21f9 --- /dev/null +++ b/react/features/chat/components/web/ChatPreview.js @@ -0,0 +1,30 @@ +// @flow +import React from 'react'; + +import AbstractChat from '../AbstractChat'; + + +type Props = { + _isOpen: Boolean +} + +type State = { + +} + +/** + * Implements a React native component that renders the chat preview + */ +export default class ChatPreview extends AbstractChatPreview { + /** + * Implements React's {@link Component#render()}. + * + * @inheritdoc + */ + render() { + + return ( +
+ ); + } +} diff --git a/react/features/chat/components/web/index.js b/react/features/chat/components/web/index.js index 18dd324100d02..d018ec2dff7af 100755 --- a/react/features/chat/components/web/index.js +++ b/react/features/chat/components/web/index.js @@ -1,5 +1,6 @@ // @flow export { default as Chat } from './Chat'; +export { default as ChatPreview } from './ChatPreview'; export { default as ChatCounter } from './ChatCounter'; export { default as ChatPrivacyDialog } from './ChatPrivacyDialog'; diff --git a/react/features/conference/components/web/Conference.js b/react/features/conference/components/web/Conference.js index af161f7cbf574..8df9f4a30a1bc 100755 --- a/react/features/conference/components/web/Conference.js +++ b/react/features/conference/components/web/Conference.js @@ -8,7 +8,7 @@ import { getConferenceNameForTitle } from '../../../base/conference'; import { connect, disconnect } from '../../../base/connection'; import { translate } from '../../../base/i18n'; import { connect as reactReduxConnect } from '../../../base/redux'; -import { Chat } from '../../../chat'; +import { ChatPreview, Chat } from '../../../chat'; import { Filmstrip } from '../../../filmstrip'; import { CalleeInfoContainer } from '../../../invite'; import { LargeVideo } from '../../../large-video'; @@ -211,8 +211,8 @@ class Conference extends AbstractConference { { this.renderNotificationsContainer() } - - { !filmstripOnly && (_showPrejoin /*|| _interimPrejoin*/ ) && } + + { !filmstripOnly && _showPrejoin /* || _interimPrejoin*/ && }
); } diff --git a/react/features/welcome/components/WelcomePage.web.js b/react/features/welcome/components/WelcomePage.web.js index 26687a7968d4d..7b86845e2f699 100755 --- a/react/features/welcome/components/WelcomePage.web.js +++ b/react/features/welcome/components/WelcomePage.web.js @@ -9,10 +9,11 @@ import { connect } from '../../base/redux'; import { CalendarList } from '../../calendar-sync'; import { RecentList } from '../../recent-list'; import { SettingsButton, SETTINGS_TABS } from '../../settings'; -import Background from './background'; + import { AbstractWelcomePage, _mapStateToProps } from './AbstractWelcomePage'; import Tabs from './Tabs'; +import Background from './background'; /** * The pattern used to validate room name. @@ -154,16 +155,15 @@ class WelcomePage extends AbstractWelcomePage { } _onRoomNameChanged(e) { - this._onRoomChange(e); - if(e.target.value.trim() != "") { + this._onRoomChange(e); + if (e.target.value.trim() != '') { this.setState({ formDisabled: false - }) - } - else { + }); + } else { this.setState({ formDisabled: true - }) + }); } } @@ -186,7 +186,7 @@ class WelcomePage extends AbstractWelcomePage { ? 'with-content' : 'without-content'}` } id = 'welcome_page'> - +
@@ -204,7 +204,7 @@ class WelcomePage extends AbstractWelcomePage {

{ t('welcomepage.enterRoomTitle') }

- {/*

+ {/*

{ t('welcomepage.subTitle') }

@@ -220,8 +220,9 @@ class WelcomePage extends AbstractWelcomePage { className = 'enter-room-input' id = 'enter_room_field' onChange = { this._onRoomNameChanged } - //pattern = { ROOM_NAME_VALIDATE_PATTERN_STR } - placeholder = { t('welcomepage.placeholderEnterRoomName') } //this.state.roomPlaceholder + + // pattern = { ROOM_NAME_VALIDATE_PATTERN_STR } + placeholder = { t('welcomepage.placeholderEnterRoomName') } // this.state.roomPlaceholder ref = { this._setRoomInputRef } title = { t('welcomepage.roomNameAllowedChars') } type = 'text' @@ -230,7 +231,7 @@ class WelcomePage extends AbstractWelcomePage {

{ @@ -240,7 +241,7 @@ class WelcomePage extends AbstractWelcomePage { }
-
{ t('welcomepage.startSession') }
+
{ t('welcomepage.startSession') }
{/* this._renderTabs() */} { showAdditionalContent From 755ff43e69d83721d20bf2ebccc20987ec8972b5 Mon Sep 17 00:00:00 2001 From: George James Date: Mon, 6 Jul 2020 01:36:45 +0400 Subject: [PATCH 2/6] Implement chat toast --- css/_chat-preview.scss | 100 ++++++++++++++ package-lock.json | 30 +++-- package.json | 1 + .../chat/components/AbstractChatPreview.js | 80 +---------- .../components/AbstractMessageContainer.js | 3 +- .../chat/components/web/ChatPreview.js | 27 +++- .../components/web/ChatPreviewContainer.js | 124 ++++++++++++++++++ .../chat/components/web/ChatPreviewGroup.js | 69 ++++++++++ react/features/chat/components/web/Expire.js | 65 +++++++++ react/features/chat/middleware.js | 4 +- 10 files changed, 409 insertions(+), 94 deletions(-) create mode 100644 react/features/chat/components/web/ChatPreviewContainer.js create mode 100755 react/features/chat/components/web/ChatPreviewGroup.js create mode 100644 react/features/chat/components/web/Expire.js diff --git a/css/_chat-preview.scss b/css/_chat-preview.scss index e69de29bb2d1d..fa636e9de3952 100644 --- a/css/_chat-preview.scss +++ b/css/_chat-preview.scss @@ -0,0 +1,100 @@ +.chat-preview { + position: absolute; + left: 47px; + top: auto; + bottom: 30px; + z-index: $zindex3; + height: 60vh; + overflow: hidden; + display: flex; + flex-direction: column; + + &::before { + content: ''; + display: block; + position: absolute; + top: 0; + left: 0; + width: 100%; + // background: linear-gradient(180deg, rgba(0,0,0,1) 0%, rgba(255,255,255,0) 71%); + + height: 50px; + } + + #chatconversation { + display: flex; + flex-direction: column-reverse; + overflow: hidden; + } +} + +.chat-preview-group { + display: flex; + flex-direction: row; + align-items: flex-end; + margin-bottom: 30px; + transition: opacity 0.3s ease-in, transform .5s ease-in; + + &.expired { + opacity: 0; + transform: scale(.5); + } + + .chatmessage { + background-color: #FFF; + border-radius: 6px 6px 6px 0px; + display: inline-block; + margin-top: 3px; + color: #393939; + } + + .chatmessage-wrapper:not(:last-child) .chatmessage { + margin-bottom: 10px; + } + + + .display-name { + display: none; + } + + &.error { + .chatmessage { + background-color: $defaultWarningColor; + border-radius: 0px; + font-weight: 100; + } + + .display-name { + display: none; + } + } + + .chatmessage-wrapper { + max-width: 100%; + + .replywrapper { + display: flex; + flex-direction: row; + align-items: center; + + .messageactions { + align-self: stretch; + border-left: 1px solid $chatActionsSeparatorColor; + display: flex; + flex-direction: column; + justify-content: center; + padding: 5px; + + .toolbox-icon { + cursor: pointer; + } + } + } + } + .avatar { + width: 45px !important; + height: 45px !important; + margin-right: 10px; + border: 2px solid #EAEAEA; + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 4ee9a2fc01225..d84108586e5dd 100755 --- a/package-lock.json +++ b/package-lock.json @@ -2843,7 +2843,7 @@ }, "@jitsi/sdp-simulcast": { "version": "0.3.0", - "resolved": "https://npr.saal.ai/@jitsi%2fsdp-simulcast/-/sdp-simulcast-0.3.0.tgz", + "resolved": "https://registry.npmjs.org/@jitsi/sdp-simulcast/-/sdp-simulcast-0.3.0.tgz", "integrity": "sha512-lxHfIWgTvdVY7F7BOcC3OaFvyvLsQJVRBCQvfmz4/Pk21/FdCyeBW4gv9ogfxxisjarU8gPX7/up4Z3C17wuXw==", "requires": { "sdp-transform": "2.3.0" @@ -5140,7 +5140,7 @@ }, "async": { "version": "0.9.0", - "resolved": "https://npr.saal.ai/async/-/async-0.9.0.tgz", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.0.tgz", "integrity": "sha1-rDYTsdqb7RtHUQu0ZRuJMeRxRsc=" }, "async-each": { @@ -6984,7 +6984,7 @@ }, "current-executing-script": { "version": "0.1.3", - "resolved": "https://npr.saal.ai/current-executing-script/-/current-executing-script-0.1.3.tgz", + "resolved": "https://registry.npmjs.org/current-executing-script/-/current-executing-script-0.1.3.tgz", "integrity": "sha1-t5jfxYtc+LAPsEwd8KwmY5Z+LHA=" }, "currently-unhandled": { @@ -11119,7 +11119,7 @@ }, "lodash.clonedeep": { "version": "4.5.0", - "resolved": "https://npr.saal.ai/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" }, "lodash.flatten": { @@ -11129,7 +11129,7 @@ }, "lodash.isequal": { "version": "4.5.0", - "resolved": "https://npr.saal.ai/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" }, "lodash.isstring": { @@ -15887,7 +15887,7 @@ }, "rtcpeerconnection-shim": { "version": "1.2.15", - "resolved": "https://npr.saal.ai/rtcpeerconnection-shim/-/rtcpeerconnection-shim-1.2.15.tgz", + "resolved": "https://registry.npmjs.org/rtcpeerconnection-shim/-/rtcpeerconnection-shim-1.2.15.tgz", "integrity": "sha512-C6DxhXt7bssQ1nHb154lqeL0SXz5Dx4RczXZu2Aa/L1NJFnEVDxFwCBo3fqtuljhHIGceg5JKBV4XJ0gW5JKyw==", "requires": { "sdp": "^2.6.0" @@ -16179,12 +16179,12 @@ }, "sdp": { "version": "2.12.0", - "resolved": "https://npr.saal.ai/sdp/-/sdp-2.12.0.tgz", + "resolved": "https://registry.npmjs.org/sdp/-/sdp-2.12.0.tgz", "integrity": "sha512-jhXqQAQVM+8Xj5EjJGVweuEzgtGWb3tmEEpl3CLP3cStInSbVHSg0QWOGQzNq8pSID4JkpeV2mPqlMDLrm0/Vw==" }, "sdp-transform": { "version": "2.3.0", - "resolved": "https://npr.saal.ai/sdp-transform/-/sdp-transform-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.3.0.tgz", "integrity": "sha1-V6lXWUIEHYV3qGnXx01MOgvYiPY=" }, "seedrandom": { @@ -17395,12 +17395,12 @@ }, "strophe.js": { "version": "1.3.4", - "resolved": "https://npr.saal.ai/strophe.js/-/strophe.js-1.3.4.tgz", + "resolved": "https://registry.npmjs.org/strophe.js/-/strophe.js-1.3.4.tgz", "integrity": "sha512-jSLDG8jolhAwGOSgiJ7DTMSYK3wVoEJHKtpVRyEacQZ6CWA6z2WRPJpcFMjsIweq5aP9/XIvKUQqHBu/ZhvESA==" }, "strophejs-plugin-disco": { "version": "0.0.2", - "resolved": "https://npr.saal.ai/strophejs-plugin-disco/-/strophejs-plugin-disco-0.0.2.tgz", + "resolved": "https://registry.npmjs.org/strophejs-plugin-disco/-/strophejs-plugin-disco-0.0.2.tgz", "integrity": "sha512-T9pJFzn1ZUqZ/we9+OvI5pFdrjeb4IBMbEjK+ZWEZV036wEl8l8GOtF8AJ3sIqOMtdIiFLdFu99JiGWd7yapAQ==" }, "strophejs-plugin-stream-management": { @@ -17955,6 +17955,14 @@ } } }, + "toastr": { + "version": "2.1.4", + "resolved": "https://npr.saal.ai/toastr/-/toastr-2.1.4.tgz", + "integrity": "sha1-i0O+ZPudDEFIcURvLbjoyk6V8YE=", + "requires": { + "jquery": ">=1.12.0" + } + }, "toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", @@ -19833,7 +19841,7 @@ }, "webrtc-adapter": { "version": "7.5.0", - "resolved": "https://npr.saal.ai/webrtc-adapter/-/webrtc-adapter-7.5.0.tgz", + "resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-7.5.0.tgz", "integrity": "sha512-cUqlw310uLLSYvO8FTNCVmGWSMlMt6vuSDkcYL1nW+RUvAILJ3jEIvAUgFQU5EFGnU+mf9/No14BFv3U+hoxBQ==", "requires": { "rtcpeerconnection-shim": "^1.2.15", diff --git a/package.json b/package.json index aa80a7b7916cd..d2776ff7576b5 100755 --- a/package.json +++ b/package.json @@ -92,6 +92,7 @@ "redux-thunk": "2.2.0", "rnnoise-wasm": "github:jitsi/rnnoise-wasm.git#db96d11f175a22ef56c7db1ba9550835b716e615", "styled-components": "3.4.9", + "toastr": "^2.1.4", "util": "0.12.1", "uuid": "^3.1.0", "windows-iana": "^3.1.0", diff --git a/react/features/chat/components/AbstractChatPreview.js b/react/features/chat/components/AbstractChatPreview.js index 5cfc792a02e3e..6408a397ab790 100644 --- a/react/features/chat/components/AbstractChatPreview.js +++ b/react/features/chat/components/AbstractChatPreview.js @@ -3,11 +3,9 @@ import { Component } from 'react'; import type { Dispatch } from 'redux'; -// import { getLocalParticipant } from '../../base/participants'; +import { getLocalParticipant } from '../../base/participants'; import { sendMessage, toggleChat, setPrivateMessageRecipient, markAsRead, markPublicAsRead } from '../actions'; -// import { getUnreadSinceLastRead } from '../functions'; - /** * The type of the React {@code Component} props of {@code AbstractChatPreview}. */ @@ -23,67 +21,10 @@ export type Props = { */ _messages: Array, - /** - * All the participants in the conference. - */ - _participants: Array, - - /** - * 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, - - /** - * Function to be used to set private message recipient. - */ - _setPrivateMessageRecipient: Function, - - /** - * Private message recipient. - */ - _privateMessageRecipient: Object, - /** * The local participant. */ _localParticipant: Object, - - /** - * Messages since last read . - */ - _messagesSinceLastRead: Array, - - /** - * Mark message as read - */ - _markAsRead: Function, - - /** - * Mark non-private message as read - */ - _markPublicAsRead: Function, }; /** @@ -152,25 +93,16 @@ export function _mapDispatchToProps(dispatch: Dispatch) { */ export function _mapStateToProps(state: Object) { const { - // isOpen, + isOpen, messages - - // privateMessageRecipient } = state['features/chat']; - // const participants = state['features/base/participants']; - // const _localParticipant = getLocalParticipant(state); + const _localParticipant = getLocalParticipant(state); - // const _messagesSinceLastRead = getUnreadSinceLastRead(state); return { - // _isOpen: isOpen, - _messages: messages - - // _participants: participants, - // _showNamePrompt: !_localParticipant.name, - // _localParticipant, - // _messagesSinceLastRead, - // _privateMessageRecipient: privateMessageRecipient + _isOpen: isOpen, + _messages: messages, + _localParticipant }; } diff --git a/react/features/chat/components/AbstractMessageContainer.js b/react/features/chat/components/AbstractMessageContainer.js index 91843075ed57f..9b472456cfc61 100755 --- a/react/features/chat/components/AbstractMessageContainer.js +++ b/react/features/chat/components/AbstractMessageContainer.js @@ -7,7 +7,8 @@ export type Props = { /** * The messages array to render. */ - messages: Array + messages: Array, + localParticipant: ?Object } /** diff --git a/react/features/chat/components/web/ChatPreview.js b/react/features/chat/components/web/ChatPreview.js index ab4b276ea21f9..4fea70edab427 100644 --- a/react/features/chat/components/web/ChatPreview.js +++ b/react/features/chat/components/web/ChatPreview.js @@ -1,12 +1,16 @@ // @flow import React from 'react'; -import AbstractChat from '../AbstractChat'; +import { translate } from '../../../base/i18n'; +import { connect } from '../../../base/redux'; +import AbstractChatPreview, { + type Props, + _mapDispatchToProps, + _mapStateToProps +} from '../AbstractChatPreview'; +import ChatPreviewContainer from './ChatPreviewContainer'; -type Props = { - _isOpen: Boolean -} type State = { @@ -15,7 +19,7 @@ type State = { /** * Implements a React native component that renders the chat preview */ -export default class ChatPreview extends AbstractChatPreview { +class ChatPreview extends AbstractChatPreview { /** * Implements React's {@link Component#render()}. * @@ -23,8 +27,19 @@ export default class ChatPreview extends AbstractChatPreview { */ render() { + if (this.props._isOpen) { + return null; + } + return ( -
+
+ +
); } } + + +export default translate(connect(_mapStateToProps, _mapDispatchToProps)(ChatPreview)); diff --git a/react/features/chat/components/web/ChatPreviewContainer.js b/react/features/chat/components/web/ChatPreviewContainer.js new file mode 100644 index 0000000000000..296a35fdc1ac1 --- /dev/null +++ b/react/features/chat/components/web/ChatPreviewContainer.js @@ -0,0 +1,124 @@ +// @flow + +import React from 'react'; + +import AbstractMessageContainer, { type Props } + from '../AbstractMessageContainer'; + +import ChatPreviewGroup from './ChatPreviewGroup'; + +/** + * Displays all received chat messages, grouped by sender. + * + * @extends AbstractMessageContainer + */ +export default class ChatPreviewContainer extends AbstractMessageContainer { + /** + * Whether or not chat has been scrolled to the bottom of the screen. Used + * to determine if chat should be scrolled automatically to the bottom when + * the {@code ChatInput} resizes. + */ + _isScrolledToBottom: boolean; + + /** + * Reference to the HTML element at the end of the list of displayed chat + * messages. Used for scrolling to the end of the chat messages. + */ + _messagesListEndRef: Object; + + /** + * A React ref to the HTML element containing all {@code ChatMessageGroup} + * instances. + */ + _messageListRef: Object; + + /** + * Initializes a new {@code MessageContainer} instance. + * + * @param {Props} props - The React {@code Component} props to initialize + * the new {@code MessageContainer} instance with. + */ + constructor(props: Props) { + super(props); + + this._isScrolledToBottom = true; + + this._messageListRef = React.createRef(); + this._messagesListEndRef = React.createRef(); + + this._onChatScroll = this._onChatScroll.bind(this); + } + + /** + * Implements {@code Component#render}. + * + * @inheritdoc + */ + render() { + // const groupedMessages = this._getMessagesGroupedBySender(); + + const messages = [ ...this.props.messages ] + .filter(msg => msg.senderId !== this.props.localParticipant?.id) + .reverse() + .map(message => ( + + )); + + return ( +
+ { messages } +
+
+ ); + } + + /** + * Scrolls to the bottom again if the instance had previously been scrolled + * to the bottom. This method is used when a resize has occurred below the + * instance and bottom scroll needs to be maintained. + * + * @returns {void} + */ + maybeUpdateBottomScroll() { + if (this._isScrolledToBottom) { + this.scrollToBottom(false); + } + } + + /** + * Automatically scrolls the displayed chat messages down to the latest. + * + * @param {boolean} withAnimation - Whether or not to show a scrolling + * animation. + * @returns {void} + */ + scrollToBottom(withAnimation: boolean) { + this._messagesListEndRef.current.scrollIntoView({ + behavior: withAnimation ? 'smooth' : 'auto', + block: 'nearest' + }); + } + + _getMessagesGroupedBySender: () => Array>; + + _onChatScroll: () => void; + + /** + * Callback invoked to listen to the current scroll location. + * + * @private + * @returns {void} + */ + _onChatScroll() { + console.log('assfadfasdfsfsdf'); + const element = this._messageListRef.current; + + this._isScrolledToBottom + = element.scrollHeight - element.scrollTop === element.clientHeight; + } +} diff --git a/react/features/chat/components/web/ChatPreviewGroup.js b/react/features/chat/components/web/ChatPreviewGroup.js new file mode 100755 index 0000000000000..5b8b1143ac0ca --- /dev/null +++ b/react/features/chat/components/web/ChatPreviewGroup.js @@ -0,0 +1,69 @@ +// @flow + +import React, { Component } from 'react'; + +import { Avatar } from '../../../base/avatar'; + +import ChatMessage from './ChatMessage'; +import Expire from './Expire'; + +type Props = { + + /** + * Additional CSS classes to apply to the root element. + */ + className: string, + + /** + * The message to display . + */ + message: Object, +}; + +/** + * Displays a list of chat messages. Will show only the display name for the + * first chat message and the timestamp for the last chat message. + * + * @extends React.Component + */ +class ChatPreviewGroup extends Component { + static defaultProps = { + className: '' + }; + + /** + * Implements React's {@link Component#render()}. + * + * @inheritdoc + */ + render() { + const { className, message } = this.props; + + + if (!message) { + return null; + } + + const { senderId } = message; + + return ( + + +
+ +
+ +
+
+ +
+ ); + } +} + +export default ChatPreviewGroup; diff --git a/react/features/chat/components/web/Expire.js b/react/features/chat/components/web/Expire.js new file mode 100644 index 0000000000000..470f087bfe026 --- /dev/null +++ b/react/features/chat/components/web/Expire.js @@ -0,0 +1,65 @@ +// @flow +import React from 'react'; + +type Props = { + children: Object, + timer: number +} + +type State = { + visible: boolean +} + +/** + * Renders a expireable element. + */ +export default class Expire extends React.Component { + state = { + visible: true + } + + timer = null + + /** + * ComponentDidMount. + * + * @inheritdoc + * @returns {undefined} + */ + componentDidMount() { + this.timer = setTimeout(() => { + this.setState({ visible: false }); + }, this.props.timer); + } + + /** + * ComponentWillUnmount. + * + * @inheritdoc + * @returns {undefined} + */ + componentWillUnmount() { + clearTimeout(this.timer); + } + + /** + * Implements React's {@link Component#render()}. + * + * @inheritdoc + * @returns {ReactElement} + */ + render() { + return ( + <> + {this.state.visible ? this.props.children + : React.Children.only( + React.cloneElement(this.props.children, { + className: `${this.props.children.props.className} expired` + }) + ) + } + + ); + } +} + diff --git a/react/features/chat/middleware.js b/react/features/chat/middleware.js index 6c5abd9b0010f..6bdecb4766f1f 100755 --- a/react/features/chat/middleware.js +++ b/react/features/chat/middleware.js @@ -1,4 +1,5 @@ // @flow +import toast from 'toastr'; import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app'; import { @@ -20,7 +21,7 @@ import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux'; import { playSound, registerSound, unregisterSound } from '../base/sounds'; import { isButtonEnabled, showToolbox } from '../toolbox'; -import { SEND_MESSAGE, SET_PRIVATE_MESSAGE_RECIPIENT } from './actionTypes'; +import { ADD_MESSAGE, SEND_MESSAGE, SET_PRIVATE_MESSAGE_RECIPIENT } from './actionTypes'; import { addMessage, clearMessages, toggleChat } from './actions'; import { ChatPrivacyDialog } from './components'; import { @@ -65,7 +66,6 @@ MiddlewareRegistry.register(store => next => action => { case CONFERENCE_JOINED: _addChatMsgListener(action.conference, store); break; - case SEND_MESSAGE: { const state = store.getState(); const { conference } = state['features/base/conference']; From 0176e0c3e9a33f5d880c9bf47578e5e2edac2924 Mon Sep 17 00:00:00 2001 From: George James Date: Mon, 6 Jul 2020 10:11:51 +0400 Subject: [PATCH 3/6] Change animation --- css/_chat-preview.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/css/_chat-preview.scss b/css/_chat-preview.scss index fa636e9de3952..39c9316227254 100644 --- a/css/_chat-preview.scss +++ b/css/_chat-preview.scss @@ -34,10 +34,11 @@ align-items: flex-end; margin-bottom: 30px; transition: opacity 0.3s ease-in, transform .5s ease-in; + align-items: flex-end; &.expired { opacity: 0; - transform: scale(.5); + transform: translateY(-200%); } .chatmessage { From 7b74d8dfbd0bf5194f096f3fad35efe847e0ea86 Mon Sep 17 00:00:00 2001 From: George James Date: Mon, 6 Jul 2020 11:29:20 +0400 Subject: [PATCH 4/6] Fix small issues --- css/_chat-preview.scss | 2 ++ react/features/chat/components/web/ChatPreviewContainer.js | 4 ---- react/features/chat/components/web/ChatPreviewGroup.js | 7 ++++++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/css/_chat-preview.scss b/css/_chat-preview.scss index 39c9316227254..04fcc33f5b01d 100644 --- a/css/_chat-preview.scss +++ b/css/_chat-preview.scss @@ -93,6 +93,8 @@ } } .avatar { + min-width: 45px; + min-height: 45px; width: 45px !important; height: 45px !important; margin-right: 10px; diff --git a/react/features/chat/components/web/ChatPreviewContainer.js b/react/features/chat/components/web/ChatPreviewContainer.js index 296a35fdc1ac1..ab5ab9417b47c 100644 --- a/react/features/chat/components/web/ChatPreviewContainer.js +++ b/react/features/chat/components/web/ChatPreviewContainer.js @@ -55,8 +55,6 @@ export default class ChatPreviewContainer extends AbstractMessageContainer msg.senderId !== this.props.localParticipant?.id) .reverse() @@ -104,8 +102,6 @@ export default class ChatPreviewContainer extends AbstractMessageContainer Array>; - _onChatScroll: () => void; /** diff --git a/react/features/chat/components/web/ChatPreviewGroup.js b/react/features/chat/components/web/ChatPreviewGroup.js index 5b8b1143ac0ca..70793aa53efea 100755 --- a/react/features/chat/components/web/ChatPreviewGroup.js +++ b/react/features/chat/components/web/ChatPreviewGroup.js @@ -1,5 +1,6 @@ // @flow +import truncate from 'lodash/truncate'; import React, { Component } from 'react'; import { Avatar } from '../../../base/avatar'; @@ -46,6 +47,7 @@ class ChatPreviewGroup extends Component { const { senderId } = message; + return ( {
From f40b895b8051d9a5c73e5a45f35b6bd76340cdff Mon Sep 17 00:00:00 2001 From: neehalsaal <42176363+neehalsaal@users.noreply.github.com> Date: Mon, 6 Jul 2020 18:52:11 +0400 Subject: [PATCH 5/6] Update _chat-preview.scss --- css/_chat-preview.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/css/_chat-preview.scss b/css/_chat-preview.scss index 04fcc33f5b01d..5dffbc15f391d 100644 --- a/css/_chat-preview.scss +++ b/css/_chat-preview.scss @@ -100,4 +100,4 @@ margin-right: 10px; border: 2px solid #EAEAEA; } -} \ No newline at end of file +} From 16ac7de7c3aa5ea12f4e07526026029f7b2e929f Mon Sep 17 00:00:00 2001 From: Neehal Shaikh Date: Mon, 6 Jul 2020 19:03:26 +0400 Subject: [PATCH 6/6] leftwatermark on welcome page brought back --- css/_welcome_page.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/css/_welcome_page.scss b/css/_welcome_page.scss index a79ea723a0d8e..d192a768db4ac 100755 --- a/css/_welcome_page.scss +++ b/css/_welcome_page.scss @@ -1,6 +1,9 @@ body.welcome-page { background: inherit; overflow: auto; + .leftwatermark { + display: block; + } } @media only screen and (max-width: 800px) {