feat(chat): Improve responsiveness.

* Fix toolbox buttons not displaying properly when chat is open.
* Open chat in fullscreen dialog past custom thresholds when mobile/desktop toolbox would become unusable due to chat
* Remove mobile chat check when displaying toolbox
This commit is contained in:
Mihai-Andrei Uscat 2021-01-12 15:24:55 +02:00 committed by GitHub
parent 1ab0f1993a
commit 43e655b619
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 275 additions and 85 deletions

View File

@ -17,6 +17,7 @@ textarea {
html {
height: 100%;
width: 100%;
overflow: hidden;
}
body {
@ -201,74 +202,3 @@ form {
background: rgba(0, 0, 0, .5);
border-radius: 4px;
}
.desktop-browser {
@media only screen and (max-width: $smallScreen) {
.watermark {
width: 20%;
height: 20%;
}
.new-toolbox {
.toolbox-content {
.button-group-center, .button-group-left, .button-group-right {
.toolbox-button {
.toolbox-icon {
width: 28px;
height: 28px;
svg {
width: 18px;
height: 18px;
}
}
&:nth-child(2) {
.toolbox-icon {
width: 30px;
height: 30px;
}
}
}
}
}
}
}
@media only screen and (max-width: $verySmallScreen) {
#videoResolutionLabel {
display: none;
}
.vertical-filmstrip .filmstrip {
display: none;
}
.new-toolbox {
.toolbox-content {
.button-group-center, .button-group-left, .button-group-right {
.settings-button-small-icon {
display: none;
}
.toolbox-button {
.toolbox-icon {
width: 18px;
height: 18px;
svg {
width: 12px;
height: 12px;
}
}
&:nth-child(2) {
.toolbox-icon {
width: 20px;
height: 20px;
}
}
}
}
}
}
.chrome-extension-banner {
display: none;
}
}
}

View File

@ -379,3 +379,31 @@
}
}
}
.chat-dialog {
display: flex;
flex-direction: column;
height: 100%;
margin-top: -5px; // Margin set by atlaskit.
&-header {
display: flex;
justify-content: space-between;
margin: 16px 16px 24px;
width: calc(100% - 32px);
box-sizing: border-box;
color: #fff;
font-weight: 600;
font-size: 24px;
line-height: 32px;
.jitsi-icon > svg {
cursor: pointer;
fill: #A4B8D1;
}
}
#chatconversation {
width: 100%;
}
}

View File

@ -1,3 +1,73 @@
@mixin small-button-size() {
.new-toolbox {
.toolbox-content {
.button-group-center, .button-group-left, .button-group-right {
.toolbox-button {
.toolbox-icon {
width: 28px;
height: 28px;
svg {
width: 18px;
height: 18px;
}
}
&:nth-child(2) {
.toolbox-icon {
width: 30px;
height: 30px;
}
}
}
}
}
}
}
@mixin very-small-button-size() {
.new-toolbox {
.toolbox-content {
.button-group-center, .button-group-left, .button-group-right {
.settings-button-small-icon {
display: none;
}
.toolbox-button {
.toolbox-icon {
width: 18px;
height: 18px;
svg {
width: 12px;
height: 12px;
}
}
&:nth-child(2) {
.toolbox-icon {
width: 20px;
height: 20px;
}
}
}
}
}
}
}
@mixin full-size-modal-positioner() {
height: 100%;
left: 0;
position: fixed;
top: 0;
max-width: 100%;
width: 100%;
}
@mixin full-size-modal-dialog() {
height: 100%;
max-height: 100%;
border-radius: 0;
}
@media only screen and (max-width: $verySmallScreen) {
.welcome {
display: block;
@ -65,3 +135,65 @@
}
}
}
.desktop-browser {
@media only screen and (max-width: $smallScreen) {
@include small-button-size();
}
@media only screen and (max-width: $verySmallScreen) {
@include very-small-button-size();
#videoResolutionLabel {
display: none;
}
.vertical-filmstrip .filmstrip {
display: none;
}
.chrome-extension-banner {
display: none;
}
}
&.shift-right {
@media only screen and (max-width: $smallScreen + $sidebarWidth) {
@include small-button-size()
}
@media only screen and (max-width: $verySmallScreen + $sidebarWidth) {
@include very-small-button-size();
#videoResolutionLabel {
display: none;
}
.vertical-filmstrip .filmstrip {
display: none;
}
.chrome-extension-banner {
display: none;
}
}
}
}
@media (min-width: 480px) and (max-width: 580px) {
.shift-right [class^="Modal__PositionerAbsolute"] {
@include full-size-modal-positioner();
}
.shift-right [class^="Modal__Dialog-"] {
@include full-size-modal-dialog();
}
}
@media (min-width: 580px) and (max-width: 680px) {
.mobile-browser {
&.shift-right [class^="Modal__PositionerAbsolute"] {
@include full-size-modal-positioner();
}
&.shift-right [class^="Modal__Dialog-"] {
@include full-size-modal-dialog();
}
}
}

View File

@ -3,14 +3,21 @@
import { Component } from 'react';
import type { Dispatch } from 'redux';
import { isMobileBrowser } from '../../base/environment/utils';
import { getLocalParticipant } from '../../base/participants';
import { sendMessage, toggleChat } from '../actions';
import { DESKTOP_SMALL_WIDTH_THRESHOLD, MOBILE_SMALL_WIDTH_THRESHOLD } from '../constants';
/**
* The type of the React {@code Component} props of {@code AbstractChat}.
*/
export type Props = {
/**
* Whether the chat is opened in a modal or not (computed based on window width).
*/
_isModal: boolean,
/**
* True if the chat window should be rendered.
*/
@ -106,6 +113,9 @@ export function _mapStateToProps(state: Object) {
const _localParticipant = getLocalParticipant(state);
return {
_isModal: isMobileBrowser()
? window.innerWidth <= MOBILE_SMALL_WIDTH_THRESHOLD
: window.innerWidth <= DESKTOP_SMALL_WIDTH_THRESHOLD,
_isOpen: isOpen,
_messages: messages,
_showNamePrompt: !_localParticipant.name

View File

@ -11,6 +11,7 @@ import AbstractChat, {
type Props
} from '../AbstractChat';
import ChatDialog from './ChatDialog';
import ChatInput from './ChatInput';
import DisplayNameForm from './DisplayNameForm';
import MessageContainer from './MessageContainer';
@ -151,16 +152,25 @@ class Chat extends AbstractChat<Props> {
* @returns {ReactElement | null}
*/
_renderPanelContent() {
const { _isOpen, _showNamePrompt } = this.props;
const ComponentToRender = _isOpen
? (
<>
{ this._renderChatHeader() }
{ _showNamePrompt
? <DisplayNameForm /> : this._renderChat() }
</>
)
: null;
const { _isModal, _isOpen, _showNamePrompt } = this.props;
let ComponentToRender = null;
if (_isOpen) {
if (_isModal) {
ComponentToRender = (
<ChatDialog>
{ _showNamePrompt ? <DisplayNameForm /> : this._renderChat() }
</ChatDialog>
);
} else {
ComponentToRender = (
<>
{ this._renderChatHeader() }
{ _showNamePrompt ? <DisplayNameForm /> : this._renderChat() }
</>
);
}
}
let className = '';
if (_isOpen) {

View File

@ -0,0 +1,37 @@
// @flow
import React from 'react';
import { Dialog } from '../../../base/dialog';
import Header from './ChatDialogHeader';
type Props = {
/**
* Children of the component.
*/
children: React$Node
}
/**
* Component that renders the content of the chat in a modal.
*
* @returns {React$Element<any>}
*/
function ChatDialog({ children }: Props) {
return (
<Dialog
customHeader = { Header }
disableEnter = { true }
hideCancelButton = { true }
submitDisabled = { true }
titleKey = 'chat.title'>
<div className = 'chat-dialog'>
{children}
</div>
</Dialog>
);
}
export default ChatDialog;

View File

@ -0,0 +1,42 @@
// @flow
import React from 'react';
import { translate } from '../../../base/i18n';
import { Icon, IconClose } from '../../../base/icons';
import { connect } from '../../../base/redux';
import { toggleChat } from '../../../chat';
type Props = {
/**
* Function to be called when pressing the close button.
*/
onCancel: Function,
/**
* Invoked to obtain translated strings.
*/
t: Function
};
/**
* Custom header of the {@code ChatDialog}.
*
* @returns {React$Element<any>}
*/
function Header({ onCancel, t }: Props) {
return (
<div
className = 'chat-dialog-header'>
{ t('chat.title') }
<Icon
onClick = { onCancel }
src = { IconClose } />
</div>
);
}
const mapDispatchToProps = { onCancel: toggleChat };
export default translate(connect(null, mapDispatchToProps)(Header));

View File

@ -29,3 +29,7 @@ export const MESSAGE_TYPE_LOCAL = 'local';
* The {@code messageType} of remote messages.
*/
export const MESSAGE_TYPE_REMOTE = 'remote';
export const DESKTOP_SMALL_WIDTH_THRESHOLD = 580;
export const MOBILE_SMALL_WIDTH_THRESHOLD = 680;

View File

@ -1,7 +1,6 @@
// @flow
import { hasAvailableDevices } from '../base/devices';
import { isMobileBrowser } from '../base/environment/utils';
declare var interfaceConfig: Object;
@ -49,10 +48,8 @@ export function isToolboxVisible(state: Object) {
visible
} = state['features/toolbox'];
const { audioSettingsVisible, videoSettingsVisible } = state['features/settings'];
const { isOpen } = state['features/chat'];
const isMobileChatOpen = isMobileBrowser() && isOpen;
return Boolean(!isMobileChatOpen && !iAmSipGateway && (timeoutID || visible || alwaysVisible
return Boolean(!iAmSipGateway && (timeoutID || visible || alwaysVisible
|| audioSettingsVisible || videoSettingsVisible));
}