mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-01-08 07:40:18 +00:00
Compare commits
6 Commits
android-sd
...
7393
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
961a9236fd | ||
|
|
364e63da14 | ||
|
|
27c62b3d78 | ||
|
|
51623b47f0 | ||
|
|
824cfc0c9c | ||
|
|
398e170e2d |
@@ -66,10 +66,6 @@ body, input, textarea, keygen, select, button {
|
||||
font-family: $baseFontFamily !important;
|
||||
}
|
||||
|
||||
#nowebrtc {
|
||||
display:none;
|
||||
}
|
||||
|
||||
button, input, select, textarea {
|
||||
margin: 0;
|
||||
vertical-align: baseline;
|
||||
@@ -148,21 +144,6 @@ form {
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.connected {
|
||||
color: #21B9FC;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.lastN, .disconnected {
|
||||
color: #a3a3a3;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
#inviteLinkRef {
|
||||
-webkit-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-style default OS scrollbar.
|
||||
*/
|
||||
|
||||
@@ -28,18 +28,6 @@
|
||||
margin-bottom: env(safe-area-inset-bottom, 0);
|
||||
width: 100%;
|
||||
|
||||
.drawer-toggle {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 44px;
|
||||
cursor: pointer;
|
||||
|
||||
svg {
|
||||
fill: none;
|
||||
}
|
||||
}
|
||||
|
||||
&#{&} .overflow-menu {
|
||||
margin: auto;
|
||||
font-size: 1.2em;
|
||||
@@ -65,25 +53,10 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&.unclickable {
|
||||
cursor: default;
|
||||
}
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
&.unclickable:hover {
|
||||
background: inherit;
|
||||
}
|
||||
}
|
||||
&.disabled {
|
||||
cursor: initial;
|
||||
color: #3b475c;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-text {
|
||||
max-width: 100%;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,4 @@
|
||||
/*Initialize*/
|
||||
div.loginmenu {
|
||||
position: absolute;
|
||||
margin: 0;
|
||||
padding: 5px;
|
||||
top: 40px;
|
||||
left: 20px;
|
||||
}
|
||||
|
||||
a.disabled {
|
||||
color: gray !important;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.loginmenu.extendedToolbarPopup {
|
||||
top: 20px;
|
||||
left: 40px;
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
.filmstrip-toolbox,
|
||||
.always-on-top-toolbox {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
border-radius: 3px;
|
||||
@@ -29,7 +28,3 @@
|
||||
transform: translateX(-50%);
|
||||
padding: 3px !important;
|
||||
}
|
||||
|
||||
.filmstrip-toolbox {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
.jqistates {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.jqistates h2 {
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #eee;
|
||||
font-size: 18px;
|
||||
line-height: 25px;
|
||||
text-align: center;
|
||||
color: #424242;
|
||||
}
|
||||
|
||||
.jqistates input {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.jqistates input[type='text'], input[type='password'] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
button.jqidefaultbutton #inviteLinkRef {
|
||||
color: #2c8ad2;
|
||||
}
|
||||
|
||||
#inviteLinkRef {
|
||||
-webkit-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
@@ -75,6 +75,3 @@
|
||||
margin-bottom: 36px;
|
||||
width: 100%;
|
||||
}
|
||||
.navigate-section-list-empty {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
.recordingSpinner {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.recording-dialog {
|
||||
flex: 0;
|
||||
flex-direction: column;
|
||||
@@ -50,10 +46,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.recording-switch-disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.recording-icon-container {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -156,8 +148,7 @@
|
||||
*/
|
||||
font-size: 14px;
|
||||
|
||||
.broadcast-dropdown,
|
||||
.broadcast-dropdown-trigger {
|
||||
.broadcast-dropdown {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,14 +34,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.subject-info {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
margin-bottom: 4px;
|
||||
max-width: 80%;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.details-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
||||
@@ -58,21 +58,6 @@
|
||||
z-index: $toolbarZ;
|
||||
pointer-events: none;
|
||||
|
||||
.button-group-center,
|
||||
.button-group-left,
|
||||
.button-group-right {
|
||||
display: flex;
|
||||
width: 33%;
|
||||
}
|
||||
|
||||
.button-group-center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.button-group-right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.toolbox-button-wth-dialog {
|
||||
display: inline-block;
|
||||
}
|
||||
@@ -112,16 +97,6 @@
|
||||
padding-bottom: env(safe-area-inset-bottom, 0);
|
||||
}
|
||||
|
||||
.beta-tag {
|
||||
background: #36383C;
|
||||
border-radius: 3px;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
margin-left: 8px;
|
||||
padding: 0 4px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.overflow-menu-hr {
|
||||
border-top: 1px solid #4C4D50;
|
||||
border-bottom: 0;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -24,24 +24,6 @@
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows an inline element.
|
||||
*/
|
||||
.show-inline {
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a flex element.
|
||||
*/
|
||||
.show-flex {
|
||||
display: -webkit-box !important;
|
||||
display: -moz-box !important;
|
||||
display: -ms-flexbox !important;
|
||||
display: -webkit-flex !important;
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* resets default button styles,
|
||||
* mostly intended to be used on interactive elements that
|
||||
|
||||
@@ -209,23 +209,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
#reloadPresentation {
|
||||
display: none;
|
||||
position: absolute;
|
||||
color: #FFFFFF;
|
||||
top: 0;
|
||||
right:0;
|
||||
padding: 10px 10px;
|
||||
font-size: 11pt;
|
||||
cursor: pointer;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 5px;
|
||||
background-clip: padding-box;
|
||||
-webkit-border-radius: 5px;
|
||||
-webkit-background-clip: padding-box;
|
||||
z-index: 20; /*The reload button should appear on top of the header!*/
|
||||
}
|
||||
|
||||
#dominantSpeaker {
|
||||
visibility: hidden;
|
||||
width: 300px;
|
||||
@@ -236,10 +219,6 @@
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
#mixedstream {
|
||||
display:none !important;
|
||||
}
|
||||
|
||||
#dominantSpeakerAvatarContainer,
|
||||
.dynamic-shadow {
|
||||
width: 200px;
|
||||
@@ -309,11 +288,6 @@
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.videoMessageFilter {
|
||||
-webkit-filter: grayscale(.5) opacity(0.8);
|
||||
filter: grayscale(.5) opacity(0.8);
|
||||
}
|
||||
|
||||
#remotePresenceMessage,
|
||||
#remoteConnectionMessage {
|
||||
position: absolute;
|
||||
|
||||
@@ -1,169 +0,0 @@
|
||||
import { Component } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import { getLocalParticipant } from '../../base/participants/functions';
|
||||
import { sendMessage, setIsPollsTabFocused } from '../actions';
|
||||
import { SMALL_WIDTH_THRESHOLD } from '../constants';
|
||||
import { IMessage } from '../reducer';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@code AbstractChat}.
|
||||
*/
|
||||
export interface IProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
_isOpen: boolean;
|
||||
|
||||
/**
|
||||
* True if the polls feature is enabled.
|
||||
*/
|
||||
_isPollsEnabled: boolean;
|
||||
|
||||
/**
|
||||
* Whether the poll tab is focused or not.
|
||||
*/
|
||||
_isPollsTabFocused: boolean;
|
||||
|
||||
/**
|
||||
* All the chat messages in the conference.
|
||||
*/
|
||||
_messages: IMessage[];
|
||||
|
||||
/**
|
||||
* Number of unread chat messages.
|
||||
*/
|
||||
_nbUnreadMessages: number;
|
||||
|
||||
/**
|
||||
* Number of unread poll messages.
|
||||
*/
|
||||
_nbUnreadPolls: number;
|
||||
|
||||
/**
|
||||
* Function to send a text message.
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
_onSendMessage: Function;
|
||||
|
||||
/**
|
||||
* Function to toggle the chat window.
|
||||
*/
|
||||
_onToggleChat: Function;
|
||||
|
||||
/**
|
||||
* Function to display the chat tab.
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
_onToggleChatTab: Function;
|
||||
|
||||
/**
|
||||
* Function to display the polls tab.
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
_onTogglePollsTab: Function;
|
||||
|
||||
/**
|
||||
* Whether or not to block chat access with a nickname input form.
|
||||
*/
|
||||
_showNamePrompt: boolean;
|
||||
|
||||
/**
|
||||
* The Redux dispatch function.
|
||||
*/
|
||||
dispatch: IStore['dispatch'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements an abstract chat panel.
|
||||
*/
|
||||
export default class AbstractChat<P extends IProps> extends Component<P> {
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onSendMessage = this._onSendMessage.bind(this);
|
||||
this._onToggleChatTab = this._onToggleChatTab.bind(this);
|
||||
this._onTogglePollsTab = this._onTogglePollsTab.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the Chat tab.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onToggleChatTab() {
|
||||
this.props.dispatch(setIsPollsTabFocused(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the Polls tab.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onTogglePollsTab() {
|
||||
this.props.dispatch(setIsPollsTabFocused(true));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to {@link Chat} React {@code Component}
|
||||
* props.
|
||||
*
|
||||
* @param {Object} state - The redux store/state.
|
||||
* @param {any} _ownProps - Components' own props.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _isOpen: boolean,
|
||||
* _messages: Array<Object>,
|
||||
* _showNamePrompt: boolean
|
||||
* }}
|
||||
*/
|
||||
export function _mapStateToProps(state: IReduxState, _ownProps: any) {
|
||||
const { isOpen, isPollsTabFocused, messages, nbUnreadMessages } = state['features/chat'];
|
||||
const { nbUnreadPolls } = state['features/polls'];
|
||||
const _localParticipant = getLocalParticipant(state);
|
||||
const { disablePolls } = state['features/base/config'];
|
||||
|
||||
return {
|
||||
_isModal: window.innerWidth <= SMALL_WIDTH_THRESHOLD,
|
||||
_isOpen: isOpen,
|
||||
_isPollsEnabled: !disablePolls,
|
||||
_isPollsTabFocused: isPollsTabFocused,
|
||||
_messages: messages,
|
||||
_nbUnreadMessages: nbUnreadMessages,
|
||||
_nbUnreadPolls: nbUnreadPolls,
|
||||
_showNamePrompt: !_localParticipant?.name
|
||||
};
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { WithTranslation } from 'react-i18next';
|
||||
|
||||
import { getLocalizedDateFormatter } from '../../base/i18n/dateUtil';
|
||||
import { MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL } from '../constants';
|
||||
import { IMessage } from '../reducer';
|
||||
import { IMessage } from '../types';
|
||||
|
||||
/**
|
||||
* Formatter string to display the message timestamp.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Component } from 'react';
|
||||
|
||||
import { IMessage } from '../reducer';
|
||||
import { IMessage } from '../types';
|
||||
|
||||
export interface IProps {
|
||||
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
/* eslint-disable react/no-multi-comp */
|
||||
import { Route, useIsFocused } from '@react-navigation/native';
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { Component, useEffect } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import JitsiScreen from '../../../base/modal/components/JitsiScreen';
|
||||
import { TabBarLabelCounter } from '../../../mobile/navigation/components/TabBarLabelCounter';
|
||||
import { closeChat } from '../../actions.native';
|
||||
import AbstractChat, {
|
||||
IProps as AbstractProps,
|
||||
_mapStateToProps
|
||||
} from '../AbstractChat';
|
||||
import { closeChat, sendMessage } from '../../actions.native';
|
||||
import { IProps as AbstractProps } from '../../types';
|
||||
|
||||
import ChatInputBar from './ChatInputBar';
|
||||
import MessageContainer from './MessageContainer';
|
||||
@@ -34,7 +32,21 @@ interface IProps extends AbstractProps {
|
||||
* Implements a React native component that renders the chat window (modal) of
|
||||
* the mobile client.
|
||||
*/
|
||||
class Chat extends AbstractChat<IProps> {
|
||||
class Chat extends Component<IProps> {
|
||||
|
||||
/**
|
||||
* Initializes a new {@code AbstractChat} instance.
|
||||
*
|
||||
* @param {Props} props - The React {@code Component} props to initialize
|
||||
* the new {@code AbstractChat} instance with.
|
||||
*/
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onSendMessage = this._onSendMessage.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
@@ -57,6 +69,39 @@ class Chat extends AbstractChat<IProps> {
|
||||
</JitsiScreen>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to {@link Chat} React {@code Component}
|
||||
* props.
|
||||
*
|
||||
* @param {Object} state - The redux store/state.
|
||||
* @param {any} _ownProps - Components' own props.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _messages: Array<Object>,
|
||||
* _nbUnreadMessages: number
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState, _ownProps: any) {
|
||||
const { messages, nbUnreadMessages } = state['features/chat'];
|
||||
|
||||
return {
|
||||
_messages: messages,
|
||||
_nbUnreadMessages: nbUnreadMessages
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)((props: IProps) => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { Component } from 'react';
|
||||
import { FlatList } from 'react-native';
|
||||
|
||||
import { MESSAGE_TYPE_LOCAL, MESSAGE_TYPE_REMOTE } from '../../constants';
|
||||
import { IMessage } from '../../reducer';
|
||||
import { IMessage } from '../../types';
|
||||
|
||||
import ChatMessage from './ChatMessage';
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { FlatList, Text, TextStyle, View, ViewStyle } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { IMessage } from '../../reducer';
|
||||
import { IMessage } from '../../types';
|
||||
import AbstractMessageContainer, { IProps as AbstractProps } from '../AbstractMessageContainer';
|
||||
|
||||
import ChatMessageGroup from './ChatMessageGroup';
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { getLocalParticipant } from '../../../base/participants/functions';
|
||||
import Tabs from '../../../base/ui/components/web/Tabs';
|
||||
import PollsPane from '../../../polls/components/web/PollsPane';
|
||||
import { toggleChat } from '../../actions.web';
|
||||
import { CHAT_TABS } from '../../constants';
|
||||
import AbstractChat, {
|
||||
IProps,
|
||||
_mapStateToProps
|
||||
} from '../AbstractChat';
|
||||
import { sendMessage, setIsPollsTabFocused, toggleChat } from '../../actions.web';
|
||||
import { CHAT_TABS, SMALL_WIDTH_THRESHOLD } from '../../constants';
|
||||
import { IProps as AbstractProps } from '../../types';
|
||||
|
||||
import ChatHeader from './ChatHeader';
|
||||
import ChatInput from './ChatInput';
|
||||
@@ -19,75 +18,101 @@ import KeyboardAvoider from './KeyboardAvoider';
|
||||
import MessageContainer from './MessageContainer';
|
||||
import MessageRecipient from './MessageRecipient';
|
||||
|
||||
/**
|
||||
* React Component for holding the chat feature in a side panel that slides in
|
||||
* and out of view.
|
||||
*/
|
||||
class Chat extends AbstractChat<IProps> {
|
||||
interface IProps extends AbstractProps {
|
||||
|
||||
/**
|
||||
* Reference to the React Component for displaying chat messages. Used for
|
||||
* scrolling to the end of the chat messages.
|
||||
* Whether the chat is opened in a modal or not (computed based on window width).
|
||||
*/
|
||||
_messageContainerRef: Object;
|
||||
_isModal: boolean;
|
||||
|
||||
/**
|
||||
* Initializes a new {@code Chat} instance.
|
||||
* True if the chat window should be rendered.
|
||||
*/
|
||||
_isOpen: boolean;
|
||||
|
||||
/**
|
||||
* True if the polls feature is enabled.
|
||||
*/
|
||||
_isPollsEnabled: boolean;
|
||||
|
||||
/**
|
||||
* Whether the poll tab is focused or not.
|
||||
*/
|
||||
_isPollsTabFocused: boolean;
|
||||
|
||||
/**
|
||||
* Number of unread poll messages.
|
||||
*/
|
||||
_nbUnreadPolls: number;
|
||||
|
||||
/**
|
||||
* Function to send a text message.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
* @protected
|
||||
*/
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this._messageContainerRef = React.createRef();
|
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onChatTabKeyDown = this._onChatTabKeyDown.bind(this);
|
||||
this._onEscClick = this._onEscClick.bind(this);
|
||||
this._onPollsTabKeyDown = this._onPollsTabKeyDown.bind(this);
|
||||
this._onToggleChat = this._onToggleChat.bind(this);
|
||||
this._onChangeTab = this._onChangeTab.bind(this);
|
||||
}
|
||||
_onSendMessage: Function;
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
* Function to toggle the chat window.
|
||||
*/
|
||||
render() {
|
||||
const { _isOpen, _isPollsEnabled, _showNamePrompt } = this.props;
|
||||
|
||||
return (
|
||||
_isOpen ? <div
|
||||
className = 'sideToolbarContainer'
|
||||
id = 'sideToolbarContainer'
|
||||
onKeyDown = { this._onEscClick } >
|
||||
<ChatHeader
|
||||
className = 'chat-header'
|
||||
isPollsEnabled = { _isPollsEnabled }
|
||||
onCancel = { this._onToggleChat } />
|
||||
{ _showNamePrompt
|
||||
? <DisplayNameForm isPollsEnabled = { _isPollsEnabled } />
|
||||
: this._renderChat() }
|
||||
</div> : null
|
||||
);
|
||||
}
|
||||
_onToggleChat: Function;
|
||||
|
||||
/**
|
||||
* Key press handler for the chat tab.
|
||||
* Function to display the chat tab.
|
||||
*
|
||||
* @param {KeyboardEvent} event - The event.
|
||||
* @returns {void}
|
||||
* @protected
|
||||
*/
|
||||
_onChatTabKeyDown(event: React.KeyboardEvent) {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this._onToggleChatTab();
|
||||
}
|
||||
}
|
||||
_onToggleChatTab: Function;
|
||||
|
||||
/**
|
||||
* Function to display the polls tab.
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
_onTogglePollsTab: Function;
|
||||
|
||||
/**
|
||||
* Whether or not to block chat access with a nickname input form.
|
||||
*/
|
||||
_showNamePrompt: boolean;
|
||||
}
|
||||
|
||||
const Chat = ({
|
||||
_isModal,
|
||||
_isOpen,
|
||||
_isPollsEnabled,
|
||||
_isPollsTabFocused,
|
||||
_messages,
|
||||
_nbUnreadMessages,
|
||||
_nbUnreadPolls,
|
||||
_onSendMessage,
|
||||
_onToggleChat,
|
||||
_onToggleChatTab,
|
||||
_onTogglePollsTab,
|
||||
_showNamePrompt,
|
||||
dispatch,
|
||||
t
|
||||
}: IProps) => {
|
||||
/**
|
||||
* Sends a text message.
|
||||
*
|
||||
* @private
|
||||
* @param {string} text - The text message to be sent.
|
||||
* @returns {void}
|
||||
* @type {Function}
|
||||
*/
|
||||
const onSendMessage = useCallback((text: string) => {
|
||||
dispatch(sendMessage(text));
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Toggles the chat window.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
const onToggleChat = useCallback(() => {
|
||||
dispatch(toggleChat());
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Click handler for the chat sidenav.
|
||||
@@ -95,27 +120,23 @@ class Chat extends AbstractChat<IProps> {
|
||||
* @param {KeyboardEvent} event - Esc key click to close the popup.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onEscClick(event: React.KeyboardEvent) {
|
||||
if (event.key === 'Escape' && this.props._isOpen) {
|
||||
const _onEscClick = useCallback((event: React.KeyboardEvent) => {
|
||||
if (event.key === 'Escape' && _isOpen) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this._onToggleChat();
|
||||
onToggleChat();
|
||||
}
|
||||
}
|
||||
}, [ _isOpen ]);
|
||||
|
||||
/**
|
||||
* Key press handler for the polls tab.
|
||||
* Change selected tab.
|
||||
*
|
||||
* @param {KeyboardEvent} event - The event.
|
||||
* @param {string} id - Id of the clicked tab.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onPollsTabKeyDown(event: React.KeyboardEvent) {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this._onTogglePollsTab();
|
||||
}
|
||||
}
|
||||
const _onChangeTab = useCallback((id: string) => {
|
||||
dispatch(setIsPollsTabFocused(id !== CHAT_TABS.CHAT));
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Returns a React Element for showing chat messages and a form to send new
|
||||
@@ -124,12 +145,10 @@ class Chat extends AbstractChat<IProps> {
|
||||
* @private
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderChat() {
|
||||
const { _isPollsEnabled, _isPollsTabFocused } = this.props;
|
||||
|
||||
function _renderChat() {
|
||||
return (
|
||||
<>
|
||||
{ _isPollsEnabled && this._renderTabs() }
|
||||
{_isPollsEnabled && _renderTabs()}
|
||||
<div
|
||||
aria-labelledby = { CHAT_TABS.CHAT }
|
||||
className = { clsx(
|
||||
@@ -141,12 +160,12 @@ class Chat extends AbstractChat<IProps> {
|
||||
role = 'tabpanel'
|
||||
tabIndex = { 0 }>
|
||||
<MessageContainer
|
||||
messages = { this.props._messages } />
|
||||
messages = { _messages } />
|
||||
<MessageRecipient />
|
||||
<ChatInput
|
||||
onSend = { this._onSendMessage } />
|
||||
onSend = { onSendMessage } />
|
||||
</div>
|
||||
{ _isPollsEnabled && (
|
||||
{_isPollsEnabled && (
|
||||
<>
|
||||
<div
|
||||
aria-labelledby = { CHAT_TABS.POLLS }
|
||||
@@ -169,13 +188,11 @@ class Chat extends AbstractChat<IProps> {
|
||||
* @private
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderTabs() {
|
||||
const { _isPollsEnabled, _isPollsTabFocused, _nbUnreadMessages, _nbUnreadPolls, t } = this.props;
|
||||
|
||||
function _renderTabs() {
|
||||
return (
|
||||
<Tabs
|
||||
accessibilityLabel = { t(_isPollsEnabled ? 'chat.titleWithPolls' : 'chat.title') }
|
||||
onChange = { this._onChangeTab }
|
||||
onChange = { _onChangeTab }
|
||||
selected = { _isPollsTabFocused ? CHAT_TABS.POLLS : CHAT_TABS.CHAT }
|
||||
tabs = { [ {
|
||||
accessibilityLabel: t('chat.tabs.chat'),
|
||||
@@ -194,24 +211,56 @@ class Chat extends AbstractChat<IProps> {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the chat window.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
_onToggleChat() {
|
||||
this.props.dispatch(toggleChat());
|
||||
}
|
||||
return (
|
||||
_isOpen ? <div
|
||||
className = 'sideToolbarContainer'
|
||||
id = 'sideToolbarContainer'
|
||||
onKeyDown = { _onEscClick } >
|
||||
<ChatHeader
|
||||
className = 'chat-header'
|
||||
isPollsEnabled = { _isPollsEnabled }
|
||||
onCancel = { onToggleChat } />
|
||||
{_showNamePrompt
|
||||
? <DisplayNameForm isPollsEnabled = { _isPollsEnabled } />
|
||||
: _renderChat()}
|
||||
</div> : null
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Change selected tab.
|
||||
*
|
||||
* @param {string} id - Id of the clicked tab.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onChangeTab(id: string) {
|
||||
id === CHAT_TABS.CHAT ? this._onToggleChatTab() : this._onTogglePollsTab();
|
||||
}
|
||||
/**
|
||||
* Maps (parts of) the redux state to {@link Chat} React {@code Component}
|
||||
* props.
|
||||
*
|
||||
* @param {Object} state - The redux store/state.
|
||||
* @param {any} _ownProps - Components' own props.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _isModal: boolean,
|
||||
* _isOpen: boolean,
|
||||
* _isPollsEnabled: boolean,
|
||||
* _isPollsTabFocused: boolean,
|
||||
* _messages: Array<Object>,
|
||||
* _nbUnreadMessages: number,
|
||||
* _nbUnreadPolls: number,
|
||||
* _showNamePrompt: boolean
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState, _ownProps: any) {
|
||||
const { isOpen, isPollsTabFocused, messages, nbUnreadMessages } = state['features/chat'];
|
||||
const { nbUnreadPolls } = state['features/polls'];
|
||||
const _localParticipant = getLocalParticipant(state);
|
||||
const { disablePolls } = state['features/base/config'];
|
||||
|
||||
return {
|
||||
_isModal: window.innerWidth <= SMALL_WIDTH_THRESHOLD,
|
||||
_isOpen: isOpen,
|
||||
_isPollsEnabled: !disablePolls,
|
||||
_isPollsTabFocused: isPollsTabFocused,
|
||||
_messages: messages,
|
||||
_nbUnreadMessages: nbUnreadMessages,
|
||||
_nbUnreadPolls: nbUnreadPolls,
|
||||
_showNamePrompt: !_localParticipant?.name
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(Chat));
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from 'react';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import Avatar from '../../../base/avatar/components/Avatar';
|
||||
import { IMessage } from '../../reducer';
|
||||
import { IMessage } from '../../types';
|
||||
|
||||
import ChatMessage from './ChatMessage';
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import emojiAsciiAliases from 'react-emoji-render/data/asciiAliases';
|
||||
import { IReduxState } from '../app/types';
|
||||
import { escapeRegexp } from '../base/util/helpers';
|
||||
|
||||
import { IMessage } from './reducer';
|
||||
import { IMessage } from './types';
|
||||
|
||||
/**
|
||||
* An ASCII emoticon regexp array to find and replace old-style ASCII
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
SET_LOBBY_CHAT_RECIPIENT,
|
||||
SET_PRIVATE_MESSAGE_RECIPIENT
|
||||
} from './actionTypes';
|
||||
import { IMessage } from './types';
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
isOpen: false,
|
||||
@@ -27,20 +28,6 @@ const DEFAULT_STATE = {
|
||||
isLobbyChatActive: false
|
||||
};
|
||||
|
||||
export interface IMessage {
|
||||
displayName: string;
|
||||
error?: Object;
|
||||
id: string;
|
||||
isReaction: boolean;
|
||||
lobbyChat: boolean;
|
||||
message: string;
|
||||
messageId: string;
|
||||
messageType: string;
|
||||
privateMessage: boolean;
|
||||
recipient: string;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
export interface IChatState {
|
||||
isLobbyChatActive: boolean;
|
||||
isOpen: boolean;
|
||||
|
||||
38
react/features/chat/types.ts
Normal file
38
react/features/chat/types.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
|
||||
import { IStore } from '../app/types';
|
||||
|
||||
export interface IMessage {
|
||||
displayName: string;
|
||||
error?: Object;
|
||||
id: string;
|
||||
isReaction: boolean;
|
||||
lobbyChat: boolean;
|
||||
message: string;
|
||||
messageId: string;
|
||||
messageType: string;
|
||||
privateMessage: boolean;
|
||||
recipient: string;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@code AbstractChat}.
|
||||
*/
|
||||
export interface IProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
* All the chat messages in the conference.
|
||||
*/
|
||||
_messages: IMessage[];
|
||||
|
||||
/**
|
||||
* Number of unread chat messages.
|
||||
*/
|
||||
_nbUnreadMessages: number;
|
||||
|
||||
/**
|
||||
* The Redux dispatch function.
|
||||
*/
|
||||
dispatch: IStore['dispatch'];
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
import { Component } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
|
||||
import { IStore } from '../../app/types';
|
||||
import { updateSettings } from '../../base/settings/actions';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of
|
||||
* {@link AbstractDisplayNamePrompt}.
|
||||
*/
|
||||
export interface IProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
* Invoked to update the local participant's display name.
|
||||
*/
|
||||
dispatch: IStore['dispatch'];
|
||||
|
||||
/**
|
||||
* Function to be invoked after a successful display name change.
|
||||
*/
|
||||
onPostSubmit?: Function;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements an abstract class for {@code DisplayNamePrompt}.
|
||||
*/
|
||||
export default class AbstractDisplayNamePrompt<S>
|
||||
extends Component<IProps, S> {
|
||||
/**
|
||||
* Instantiates a new component.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this._onSetDisplayName = this._onSetDisplayName.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an action to update the local participant's display name. A
|
||||
* name must be entered for the action to dispatch.
|
||||
*
|
||||
* It returns a boolean to comply the Dialog behaviour:
|
||||
* {@code true} - the dialog should be closed.
|
||||
* {@code false} - the dialog should be left open.
|
||||
*
|
||||
* @param {string} displayName - The display name to save.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_onSetDisplayName(displayName: string) {
|
||||
if (!displayName?.trim()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { dispatch, onPostSubmit } = this.props;
|
||||
|
||||
// Store display name in settings
|
||||
dispatch(updateSettings({
|
||||
displayName
|
||||
}));
|
||||
|
||||
onPostSubmit?.();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,29 @@
|
||||
import React from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import InputDialog from '../../../base/dialog/components/native/InputDialog';
|
||||
import AbstractDisplayNamePrompt from '../AbstractDisplayNamePrompt';
|
||||
import { onSetDisplayName } from '../../functions';
|
||||
import { IProps } from '../../types';
|
||||
|
||||
/**
|
||||
* Implements a component to render a display name prompt.
|
||||
*/
|
||||
class DisplayNamePrompt extends AbstractDisplayNamePrompt<any> {
|
||||
class DisplayNamePrompt extends Component<IProps> {
|
||||
_onSetDisplayName: (displayName: string) => boolean;
|
||||
|
||||
/**
|
||||
* Initializes a new {@code DisplayNamePrompt} instance.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onSetDisplayName = onSetDisplayName(props.dispatch, props.onPostSubmit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import React from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import Dialog from '../../../base/ui/components/web/Dialog';
|
||||
import Input from '../../../base/ui/components/web/Input';
|
||||
import AbstractDisplayNamePrompt, { IProps } from '../AbstractDisplayNamePrompt';
|
||||
import { onSetDisplayName } from '../../functions';
|
||||
import { IProps } from '../../types';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link DisplayNamePrompt}.
|
||||
@@ -23,7 +24,9 @@ interface IState {
|
||||
*
|
||||
* @augments Component
|
||||
*/
|
||||
class DisplayNamePrompt extends AbstractDisplayNamePrompt<IState> {
|
||||
class DisplayNamePrompt extends Component<IProps, IState> {
|
||||
_onSetDisplayName: (displayName: string) => boolean;
|
||||
|
||||
/**
|
||||
* Initializes a new {@code DisplayNamePrompt} instance.
|
||||
*
|
||||
@@ -40,6 +43,7 @@ class DisplayNamePrompt extends AbstractDisplayNamePrompt<IState> {
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onDisplayNameChange = this._onDisplayNameChange.bind(this);
|
||||
this._onSubmit = this._onSubmit.bind(this);
|
||||
this._onSetDisplayName = onSetDisplayName(props.dispatch, props.onPostSubmit);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { IStore } from '../app/types';
|
||||
import { updateSettings } from '../base/settings/actions';
|
||||
|
||||
/**
|
||||
* Appends a suffix to the display name.
|
||||
@@ -10,3 +12,33 @@ export function appendSuffix(displayName: string, suffix = ''): string {
|
||||
return `${displayName || suffix}${
|
||||
displayName && suffix && displayName !== suffix ? ` (${suffix})` : ''}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an action to update the local participant's display name. A
|
||||
* name must be entered for the action to dispatch.
|
||||
*
|
||||
* It returns a boolean to comply the Dialog behaviour:
|
||||
* {@code true} - the dialog should be closed.
|
||||
* {@code false} - the dialog should be left open.
|
||||
*
|
||||
* @param {Function} dispatch - Redux dispatch function.
|
||||
* @param {Function} onPostSubmit - Function to be invoked after a successful display name change.
|
||||
* @param {string} displayName - The display name to save.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function onSetDisplayName(dispatch: IStore['dispatch'], onPostSubmit?: Function) {
|
||||
return function(displayName: string) {
|
||||
if (!displayName?.trim()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Store display name in settings
|
||||
dispatch(updateSettings({
|
||||
displayName
|
||||
}));
|
||||
|
||||
onPostSubmit?.();
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
20
react/features/display-name/types.ts
Normal file
20
react/features/display-name/types.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
|
||||
import { IStore } from '../app/types';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of
|
||||
* {@link AbstractDisplayNamePrompt}.
|
||||
*/
|
||||
export interface IProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
* Invoked to update the local participant's display name.
|
||||
*/
|
||||
dispatch: IStore['dispatch'];
|
||||
|
||||
/**
|
||||
* Function to be invoked after a successful display name change.
|
||||
*/
|
||||
onPostSubmit?: Function;
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
|
||||
import { IReduxState } from '../../app/types';
|
||||
|
||||
export interface IProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
* Custom e2ee labels.
|
||||
*/
|
||||
_e2eeLabels?: any;
|
||||
|
||||
/**
|
||||
* True if the label needs to be rendered, false otherwise.
|
||||
*/
|
||||
_showLabel?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to the associated props of this {@code Component}.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @private
|
||||
* @returns {IProps}
|
||||
*/
|
||||
export function _mapStateToProps(state: IReduxState) {
|
||||
const { e2ee = {} } = state['features/base/config'];
|
||||
|
||||
return {
|
||||
_e2eeLabels: e2ee.labels,
|
||||
_showLabel: state['features/base/participants'].numberOfParticipantsDisabledE2EE === 0
|
||||
};
|
||||
}
|
||||
@@ -1,13 +1,26 @@
|
||||
import React from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../app/types';
|
||||
import { translate } from '../../base/i18n/functions';
|
||||
import { IconE2EE } from '../../base/icons/svg';
|
||||
import Label from '../../base/label/components/web/Label';
|
||||
import { COLORS } from '../../base/label/constants';
|
||||
import Tooltip from '../../base/tooltip/components/Tooltip';
|
||||
|
||||
import { IProps, _mapStateToProps } from './AbstractE2EELabel';
|
||||
export interface IProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
* Custom e2ee labels.
|
||||
*/
|
||||
_e2eeLabels?: any;
|
||||
|
||||
/**
|
||||
* True if the label needs to be rendered, false otherwise.
|
||||
*/
|
||||
_showLabel?: boolean;
|
||||
}
|
||||
|
||||
|
||||
const E2EELabel = ({ _e2eeLabels, _showLabel, t }: IProps) => {
|
||||
@@ -27,4 +40,20 @@ const E2EELabel = ({ _e2eeLabels, _showLabel, t }: IProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to the associated props of this {@code Component}.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @private
|
||||
* @returns {IProps}
|
||||
*/
|
||||
export function _mapStateToProps(state: IReduxState) {
|
||||
const { e2ee = {} } = state['features/base/config'];
|
||||
|
||||
return {
|
||||
_e2eeLabels: e2ee.labels,
|
||||
_showLabel: state['features/base/participants'].numberOfParticipantsDisabledE2EE === 0
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(E2EELabel));
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { IReduxState } from '../../app/types';
|
||||
import { getParticipantById, hasRaisedHand } from '../../base/participants/functions';
|
||||
|
||||
export interface IProps {
|
||||
|
||||
/**
|
||||
* True if the hand is raised for this participant.
|
||||
*/
|
||||
_raisedHand?: boolean;
|
||||
|
||||
/**
|
||||
* The participant id who we want to render the raised hand indicator
|
||||
* for.
|
||||
*/
|
||||
participantId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements an abstract class for the RaisedHandIndicator component.
|
||||
*/
|
||||
export default abstract class AbstractRaisedHandIndicator<P extends IProps>
|
||||
extends Component<P> {
|
||||
|
||||
/**
|
||||
* Implements {@code Component#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
if (!this.props._raisedHand) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this._renderIndicator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the platform specific indicator element.
|
||||
*
|
||||
* @returns {React$Element<*>}
|
||||
*/
|
||||
abstract _renderIndicator(): React.ReactElement;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the Redux state to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @param {IProps} ownProps - The own props of the component.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function _mapStateToProps(state: IReduxState, ownProps: IProps) {
|
||||
const participant = getParticipantById(state, ownProps.participantId);
|
||||
|
||||
return {
|
||||
_raisedHand: hasRaisedHand(participant)
|
||||
};
|
||||
}
|
||||
@@ -1,23 +1,49 @@
|
||||
import React from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import { View, ViewStyle } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { IconRaiseHand } from '../../../base/icons/svg';
|
||||
import { getParticipantById, hasRaisedHand } from '../../../base/participants/functions';
|
||||
import BaseIndicator from '../../../base/react/components/native/BaseIndicator';
|
||||
import AbstractRaisedHandIndicator, {
|
||||
IProps,
|
||||
_mapStateToProps
|
||||
} from '../AbstractRaisedHandIndicator';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
export interface IProps {
|
||||
|
||||
/**
|
||||
* True if the hand is raised for this participant.
|
||||
*/
|
||||
_raisedHand?: boolean;
|
||||
|
||||
/**
|
||||
* The participant id who we want to render the raised hand indicator
|
||||
* for.
|
||||
*/
|
||||
participantId: string;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Thumbnail badge showing that the participant would like to speak.
|
||||
*
|
||||
* @augments Component
|
||||
*/
|
||||
class RaisedHandIndicator extends AbstractRaisedHandIndicator<IProps> {
|
||||
class RaisedHandIndicator extends Component<IProps> {
|
||||
|
||||
/**
|
||||
* Implements {@code Component#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
if (!this.props._raisedHand) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this._renderIndicator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the platform specific indicator element.
|
||||
*
|
||||
@@ -34,4 +60,19 @@ class RaisedHandIndicator extends AbstractRaisedHandIndicator<IProps> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the Redux state to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @param {IProps} ownProps - The own props of the component.
|
||||
* @returns {Object}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState, ownProps: IProps) {
|
||||
const participant = getParticipantById(state, ownProps.participantId);
|
||||
|
||||
return {
|
||||
_raisedHand: hasRaisedHand(participant)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(RaisedHandIndicator);
|
||||
|
||||
@@ -10,7 +10,7 @@ import { getFeatureFlag } from '../../base/flags/functions';
|
||||
import { getLocalParticipant } from '../../base/participants/functions';
|
||||
import { getFieldValue } from '../../base/react/functions';
|
||||
import { updateSettings } from '../../base/settings/actions';
|
||||
import { IMessage } from '../../chat/reducer';
|
||||
import { IMessage } from '../../chat/types';
|
||||
import { isDeviceStatusVisible } from '../../prejoin/functions';
|
||||
import { cancelKnocking, joinWithPassword, onSendMessage, setPasswordJoinFailed, startKnocking } from '../actions';
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
/**
|
||||
* The type of redux action to set the AppState API change event listener.
|
||||
* The type of redux action used for app state subscription.
|
||||
*
|
||||
* {
|
||||
* type: _SET_APP_STATE_LISTENER,
|
||||
* listener: Function
|
||||
* type: _SET_APP_STATE_SUBSCRIPTION,
|
||||
* subscription: NativeEventSubscription
|
||||
* }
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
export const _SET_APP_STATE_LISTENER = '_SET_APP_STATE_LISTENER';
|
||||
export const _SET_APP_STATE_SUBSCRIPTION = '_SET_APP_STATE_SUBSCRIPTION';
|
||||
|
||||
/**
|
||||
* The type of redux action which signals that the app state has changed (in
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
import { APP_STATE_CHANGED, _SET_APP_STATE_LISTENER } from './actionTypes';
|
||||
import { NativeEventSubscription } from 'react-native';
|
||||
|
||||
import { APP_STATE_CHANGED, _SET_APP_STATE_SUBSCRIPTION } from './actionTypes';
|
||||
|
||||
/**
|
||||
* Sets the listener to be used with React Native's AppState API.
|
||||
* Sets subscription for app state.
|
||||
*
|
||||
* @param {Function} listener - Function to be set as the change event listener.
|
||||
* @protected
|
||||
* @param {Function} subscription - Subscription for the native event.
|
||||
* @private
|
||||
* @returns {{
|
||||
* type: _SET_APP_STATE_LISTENER,
|
||||
* listener: Function
|
||||
* type: _SET_APP_STATE_SUBSCRIPTION,
|
||||
* subscription: NativeEventSubscription
|
||||
* }}
|
||||
*/
|
||||
export function _setAppStateListener(listener?: Function) {
|
||||
export function _setAppStateSubscription(subscription?: NativeEventSubscription) {
|
||||
return {
|
||||
type: _SET_APP_STATE_LISTENER,
|
||||
listener
|
||||
type: _SET_APP_STATE_SUBSCRIPTION,
|
||||
subscription
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -5,11 +5,7 @@ import { IStore } from '../../app/types';
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../base/app/actionTypes';
|
||||
import MiddlewareRegistry from '../../base/redux/MiddlewareRegistry';
|
||||
|
||||
import { _SET_APP_STATE_LISTENER } from './actionTypes';
|
||||
import {
|
||||
_setAppStateListener as _setAppStateListenerA,
|
||||
appStateChanged
|
||||
} from './actions';
|
||||
import { _setAppStateSubscription, appStateChanged } from './actions';
|
||||
|
||||
/**
|
||||
* Middleware that captures App lifetime actions and subscribes to application
|
||||
@@ -23,18 +19,16 @@ import {
|
||||
*/
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case _SET_APP_STATE_LISTENER:
|
||||
return _setAppStateListenerF(store, next, action);
|
||||
|
||||
case APP_WILL_MOUNT: {
|
||||
const { dispatch } = store;
|
||||
|
||||
dispatch(_setAppStateListenerA(_onAppStateChange.bind(undefined, dispatch)));
|
||||
_setAppStateListener(store, next, action, _onAppStateChange.bind(undefined, dispatch));
|
||||
break;
|
||||
}
|
||||
|
||||
case APP_WILL_UNMOUNT:
|
||||
store.dispatch(_setAppStateListenerA(undefined));
|
||||
_setAppStateListener(store, next, action, undefined);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -65,19 +59,16 @@ function _onAppStateChange(dispatch: IStore['dispatch'], appState: string) {
|
||||
* specified action to the specified store.
|
||||
* @param {Action} action - The redux action {@code _SET_IMMERSIVE_LISTENER}
|
||||
* which is being dispatched in the specified store.
|
||||
* @param {any} listener - Listener for app state status.
|
||||
* @private
|
||||
* @returns {Object} The value returned by {@code next(action)}.
|
||||
*/
|
||||
function _setAppStateListenerF({ getState }: IStore, next: Function, action: AnyAction) {
|
||||
// Remove the old AppState listener and add the new one.
|
||||
const { appStateListener: oldListener } = getState()['features/background'];
|
||||
function _setAppStateListener({ dispatch, getState }: IStore, next: Function, action: AnyAction, listener: any) {
|
||||
const { subscription } = getState()['features/background'];
|
||||
const result = next(action);
|
||||
const { appStateListener: newListener } = getState()['features/background'];
|
||||
|
||||
if (oldListener !== newListener) {
|
||||
oldListener && AppState.removeEventListener('change', oldListener);
|
||||
newListener && AppState.addEventListener('change', newListener);
|
||||
}
|
||||
subscription?.remove();
|
||||
listener && dispatch(_setAppStateSubscription(AppState.addEventListener('change', listener)));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import { AppStateStatus } from 'react-native';
|
||||
import { NativeEventSubscription } from 'react-native';
|
||||
|
||||
import ReducerRegistry from '../../base/redux/ReducerRegistry';
|
||||
|
||||
import {
|
||||
APP_STATE_CHANGED,
|
||||
_SET_APP_STATE_LISTENER
|
||||
} from './actionTypes';
|
||||
import { APP_STATE_CHANGED, _SET_APP_STATE_SUBSCRIPTION } from './actionTypes';
|
||||
|
||||
export interface IBackgroundState {
|
||||
appState: string;
|
||||
appStateListener?: (state: AppStateStatus) => void;
|
||||
subscription?: NativeEventSubscription;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -21,10 +18,11 @@ const DEFAULT_STATE = {
|
||||
|
||||
ReducerRegistry.register<IBackgroundState>('features/background', (state = DEFAULT_STATE, action): IBackgroundState => {
|
||||
switch (action.type) {
|
||||
case _SET_APP_STATE_LISTENER:
|
||||
|
||||
case _SET_APP_STATE_SUBSCRIPTION:
|
||||
return {
|
||||
...state,
|
||||
appStateListener: action.listener
|
||||
subscription: action.subscription
|
||||
};
|
||||
|
||||
case APP_STATE_CHANGED:
|
||||
|
||||
@@ -23,13 +23,13 @@ const externalAPIEnabled = isExternalAPIAvailable();
|
||||
|
||||
switch (type) {
|
||||
case READY_TO_CLOSE:
|
||||
rnSdkHandlers.onReadyToClose && rnSdkHandlers.onReadyToClose();
|
||||
rnSdkHandlers?.onReadyToClose && rnSdkHandlers?.onReadyToClose();
|
||||
break;
|
||||
case CONFERENCE_JOINED:
|
||||
rnSdkHandlers.onConferenceJoined && rnSdkHandlers.onConferenceJoined();
|
||||
rnSdkHandlers?.onConferenceJoined && rnSdkHandlers?.onConferenceJoined();
|
||||
break;
|
||||
case CONFERENCE_WILL_JOIN:
|
||||
rnSdkHandlers.onConferenceWillJoin && rnSdkHandlers.onConferenceWillJoin();
|
||||
rnSdkHandlers?.onConferenceWillJoin && rnSdkHandlers?.onConferenceWillJoin();
|
||||
break;
|
||||
case CONFERENCE_LEFT:
|
||||
// Props are torn down at this point, perhaps need to leave this one out
|
||||
@@ -38,7 +38,7 @@ const externalAPIEnabled = isExternalAPIAvailable();
|
||||
const { participant } = action;
|
||||
const participantInfo = participantToParticipantInfo(participant);
|
||||
|
||||
rnSdkHandlers.onParticipantJoined && rnSdkHandlers.onParticipantJoined(participantInfo);
|
||||
rnSdkHandlers?.onParticipantJoined && rnSdkHandlers?.onParticipantJoined(participantInfo);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,49 @@
|
||||
import React from 'react';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import Tooltip from '../../../base/tooltip/components/Tooltip';
|
||||
import { TOOLTIP_POSITION } from '../../../base/ui/constants.any';
|
||||
import AbstractToolbarButton, {
|
||||
IProps as AbstractToolbarButtonProps
|
||||
} from '../../../toolbox/components/AbstractToolbarButton';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link ReactionButton}.
|
||||
*/
|
||||
interface IProps extends AbstractToolbarButtonProps {
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* A succinct description of what the button does. Used by accessibility
|
||||
* tools and torture tests.
|
||||
*/
|
||||
accessibilityLabel: string;
|
||||
|
||||
/**
|
||||
* The Icon of this {@code AbstractToolbarButton}.
|
||||
*/
|
||||
icon: Object;
|
||||
|
||||
/**
|
||||
* The style of the Icon of this {@code AbstractToolbarButton}.
|
||||
*/
|
||||
iconStyle?: Object;
|
||||
|
||||
/**
|
||||
* Optional label for the button.
|
||||
*/
|
||||
label?: string;
|
||||
|
||||
/**
|
||||
* On click handler.
|
||||
*/
|
||||
onClick: Function;
|
||||
|
||||
/**
|
||||
* {@code AbstractToolbarButton} Styles.
|
||||
*/
|
||||
style?: Array<string> | Object;
|
||||
|
||||
/**
|
||||
* An optional modifier to render the button toggled.
|
||||
*/
|
||||
toggled?: boolean;
|
||||
|
||||
/**
|
||||
* Optional text to display in the tooltip.
|
||||
*/
|
||||
@@ -26,6 +54,11 @@ interface IProps extends AbstractToolbarButtonProps {
|
||||
* button.
|
||||
*/
|
||||
tooltipPosition: TOOLTIP_POSITION;
|
||||
|
||||
/**
|
||||
* The color underlying the button.
|
||||
*/
|
||||
underlayColor?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,7 +82,7 @@ interface IState {
|
||||
*
|
||||
* @augments AbstractToolbarButton
|
||||
*/
|
||||
class ReactionButton extends AbstractToolbarButton<IProps, IState> {
|
||||
class ReactionButton extends Component<IProps, IState> {
|
||||
/**
|
||||
* Default values for {@code ReactionButton} component's properties.
|
||||
*
|
||||
@@ -69,6 +102,7 @@ class ReactionButton extends AbstractToolbarButton<IProps, IState> {
|
||||
|
||||
this._onKeyDown = this._onKeyDown.bind(this);
|
||||
this._onClickHandler = this._onClickHandler.bind(this);
|
||||
this._onClick = this._onClick.bind(this);
|
||||
|
||||
this.state = {
|
||||
increaseLevel: 0,
|
||||
@@ -76,6 +110,20 @@ class ReactionButton extends AbstractToolbarButton<IProps, IState> {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles clicking/pressing this {@code AbstractToolbarButton} by
|
||||
* forwarding the event to the {@code onClick} prop of this instance if any.
|
||||
*
|
||||
* @protected
|
||||
* @returns {*} The result returned by the invocation of the {@code onClick}
|
||||
* prop of this instance if any.
|
||||
*/
|
||||
_onClick(...args: any) {
|
||||
const { onClick } = this.props;
|
||||
|
||||
return onClick?.(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles 'Enter' key on the button to trigger onClick for accessibility.
|
||||
* We should be handling Space onKeyUp but it conflicts with PTT.
|
||||
@@ -168,6 +216,16 @@ class ReactionButton extends AbstractToolbarButton<IProps, IState> {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
return this._renderButton(this._renderIcon());
|
||||
}
|
||||
}
|
||||
|
||||
export default ReactionButton;
|
||||
|
||||
@@ -203,7 +203,7 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
|
||||
return {
|
||||
...newProps,
|
||||
selectedVideoInputId: videoTabState.selectedVideoInputId || newProps.selectedVideoInputId,
|
||||
selectedVideoInputId: videoTabState?.selectedVideoInputId || newProps.selectedVideoInputId,
|
||||
options: tabState.options
|
||||
};
|
||||
},
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
import React, { Component, ReactElement } from 'react';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of
|
||||
* {@link AbstractToolbarButton}.
|
||||
*/
|
||||
export interface IProps {
|
||||
|
||||
/**
|
||||
* A succinct description of what the button does. Used by accessibility
|
||||
* tools and torture tests.
|
||||
*/
|
||||
accessibilityLabel: string;
|
||||
|
||||
/**
|
||||
* The Icon of this {@code AbstractToolbarButton}.
|
||||
*/
|
||||
icon: Object;
|
||||
|
||||
/**
|
||||
* The style of the Icon of this {@code AbstractToolbarButton}.
|
||||
*/
|
||||
iconStyle?: Object;
|
||||
|
||||
/**
|
||||
* On click handler.
|
||||
*/
|
||||
onClick: Function;
|
||||
|
||||
/**
|
||||
* {@code AbstractToolbarButton} Styles.
|
||||
*/
|
||||
style?: Array<string> | Object;
|
||||
|
||||
/**
|
||||
* An optional modifier to render the button toggled.
|
||||
*/
|
||||
toggled?: boolean;
|
||||
|
||||
/**
|
||||
* The color underlying the button.
|
||||
*/
|
||||
underlayColor?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract (base) class for a button in {@link Toolbar}.
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
export default class AbstractToolbarButton<P extends IProps, State=void> extends Component<P, State> {
|
||||
/**
|
||||
* Initializes a new {@code AbstractToolbarButton} instance.
|
||||
*
|
||||
* @param {Object} props - The React {@code Component} props to initialize
|
||||
* the new {@code AbstractToolbarButton} instance with.
|
||||
*/
|
||||
constructor(props: P) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onClick = this._onClick.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles clicking/pressing this {@code AbstractToolbarButton} by
|
||||
* forwarding the event to the {@code onClick} prop of this instance if any.
|
||||
*
|
||||
* @protected
|
||||
* @returns {*} The result returned by the invocation of the {@code onClick}
|
||||
* prop of this instance if any.
|
||||
*/
|
||||
_onClick(...args: any) {
|
||||
const { onClick } = this.props;
|
||||
|
||||
return onClick?.(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
return this._renderButton(this._renderIcon());
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a button element.
|
||||
*
|
||||
* @param {ReactElement | null} _el - The element to render inside the button.
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderButton(_el: ReactElement | null): React.ReactElement | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render an icon element.
|
||||
*
|
||||
* @returns {ReactElement | null}
|
||||
*/
|
||||
_renderIcon(): React.ReactElement | null {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import { IReduxState } from '../../app/types';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link TranscribingLabel}.
|
||||
*/
|
||||
export interface IProps {
|
||||
|
||||
/**
|
||||
* True if the label needs to be rendered, false otherwise.
|
||||
*/
|
||||
_showLabel: boolean;
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to the associated props of the
|
||||
* {@link AbstractTranscribingLabel} {@code Component}.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _showLabel: boolean
|
||||
* }}
|
||||
*/
|
||||
export function _mapStateToProps(state: IReduxState) {
|
||||
return {
|
||||
_showLabel: state['features/transcribing'].isTranscribing
|
||||
};
|
||||
}
|
||||
@@ -1,10 +1,22 @@
|
||||
import React, { Component } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../app/types';
|
||||
import { translate } from '../../base/i18n/functions';
|
||||
import Label from '../../base/label/components/native/Label';
|
||||
|
||||
import { IProps, _mapStateToProps } from './AbstractTranscribingLabel';
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link TranscribingLabel}.
|
||||
*/
|
||||
export interface IProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
* True if the label needs to be rendered, false otherwise.
|
||||
*/
|
||||
_showLabel: boolean;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* React {@code Component} for displaying a label when a transcriber is in the
|
||||
@@ -28,4 +40,20 @@ class TranscribingLabel extends Component<IProps> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to the associated props of the
|
||||
* {@link AbstractTranscribingLabel} {@code Component}.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _showLabel: boolean
|
||||
* }}
|
||||
*/
|
||||
export function _mapStateToProps(state: IReduxState) {
|
||||
return {
|
||||
_showLabel: state['features/transcribing'].isTranscribing
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(TranscribingLabel));
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { translate } from '../../base/i18n/functions';
|
||||
import { IReduxState } from '../../app/types';
|
||||
import Label from '../../base/label/components/web/Label';
|
||||
import Tooltip from '../../base/tooltip/components/Tooltip';
|
||||
|
||||
import { IProps, _mapStateToProps } from './AbstractTranscribingLabel';
|
||||
const TranscribingLabel = () => {
|
||||
const _showLabel = useSelector((state: IReduxState) => state['features/transcribing'].isTranscribing);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const TranscribingLabel = ({ _showLabel, t }: IProps) => {
|
||||
if (!_showLabel) {
|
||||
return null;
|
||||
}
|
||||
@@ -23,4 +25,4 @@ const TranscribingLabel = ({ _showLabel, t }: IProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default translate(connect(_mapStateToProps)(TranscribingLabel));
|
||||
export default TranscribingLabel;
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import { Component } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
|
||||
import { IReduxState } from '../../app/types';
|
||||
|
||||
export interface IProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
* Whether or not the conference is in audio only mode.
|
||||
*/
|
||||
_audioOnly: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract class for the {@code VideoQualityLabel} component.
|
||||
*/
|
||||
export default class AbstractVideoQualityLabel<P extends IProps> extends Component<P> {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated
|
||||
* {@code AbstractVideoQualityLabel}'s props.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _audioOnly: boolean
|
||||
* }}
|
||||
*/
|
||||
export function _abstractMapStateToProps(state: IReduxState) {
|
||||
const { enabled: audioOnly } = state['features/base/audio-only'];
|
||||
|
||||
return {
|
||||
_audioOnly: audioOnly
|
||||
};
|
||||
}
|
||||
@@ -1,17 +1,20 @@
|
||||
import React from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../app/types';
|
||||
import { translate } from '../../base/i18n/functions';
|
||||
import Label from '../../base/label/components/native/Label';
|
||||
import { StyleType, combineStyles } from '../../base/styles/functions.native';
|
||||
|
||||
import AbstractVideoQualityLabel, {
|
||||
IProps as AbstractProps,
|
||||
_abstractMapStateToProps
|
||||
} from './AbstractVideoQualityLabel';
|
||||
import styles from './styles';
|
||||
|
||||
interface IProps extends AbstractProps {
|
||||
interface IProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
* Whether or not the conference is in audio only mode.
|
||||
*/
|
||||
_audioOnly: boolean;
|
||||
|
||||
/**
|
||||
* Style of the component passed as props.
|
||||
@@ -28,7 +31,7 @@ interface IProps extends AbstractProps {
|
||||
* is kept consistent with web and in the future we may introduce the required
|
||||
* api and extend this component with actual quality indication.
|
||||
*/
|
||||
class VideoQualityLabel extends AbstractVideoQualityLabel<IProps> {
|
||||
class VideoQualityLabel extends Component<IProps> {
|
||||
|
||||
/**
|
||||
* Implements React {@link Component}'s render.
|
||||
@@ -51,4 +54,22 @@ class VideoQualityLabel extends AbstractVideoQualityLabel<IProps> {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(connect(_abstractMapStateToProps)(VideoQualityLabel));
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated
|
||||
* {@code AbstractVideoQualityLabel}'s props.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _audioOnly: boolean
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
const { enabled: audioOnly } = state['features/base/audio-only'];
|
||||
|
||||
return {
|
||||
_audioOnly: audioOnly
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(VideoQualityLabel));
|
||||
|
||||
@@ -1,116 +1,67 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import { IReduxState } from '../../app/types';
|
||||
import { openDialog } from '../../base/dialog/actions';
|
||||
import { translate } from '../../base/i18n/functions';
|
||||
import { IconPerformance } from '../../base/icons/svg';
|
||||
import Label from '../../base/label/components/web/Label';
|
||||
import { COLORS } from '../../base/label/constants';
|
||||
import Tooltip from '../../base/tooltip/components/Tooltip';
|
||||
import { shouldDisplayTileView } from '../../video-layout/functions.web';
|
||||
|
||||
import AbstractVideoQualityLabel, {
|
||||
IProps as AbstractProps,
|
||||
_abstractMapStateToProps
|
||||
} from './AbstractVideoQualityLabel';
|
||||
import VideoQualityDialog from './VideoQualityDialog.web';
|
||||
|
||||
interface IProps extends AbstractProps {
|
||||
|
||||
/**
|
||||
* The message to show within the label's tooltip.
|
||||
*/
|
||||
_tooltipKey: string;
|
||||
|
||||
/**
|
||||
* Flag controlling visibility of the component.
|
||||
*/
|
||||
_visible: boolean;
|
||||
|
||||
/**
|
||||
* The redux dispatch function.
|
||||
*/
|
||||
dispatch: IStore['dispatch'];
|
||||
}
|
||||
|
||||
/**
|
||||
* React {@code Component} responsible for displaying a label that indicates
|
||||
* the displayed video state of the current conference. {@code AudioOnlyLabel}
|
||||
* Will display when the conference is in audio only mode. {@code HDVideoLabel}
|
||||
* Will display if not in audio only mode and a high-definition large video is
|
||||
* being displayed.
|
||||
*/
|
||||
export class VideoQualityLabel extends AbstractVideoQualityLabel<IProps> {
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const {
|
||||
_audioOnly,
|
||||
_visible,
|
||||
dispatch,
|
||||
t
|
||||
} = this.props;
|
||||
|
||||
|
||||
if (!_visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let className, icon, labelContent, tooltipKey;
|
||||
|
||||
if (_audioOnly) {
|
||||
className = 'audio-only';
|
||||
labelContent = t('videoStatus.audioOnly');
|
||||
tooltipKey = 'videoStatus.labelTooltipAudioOnly';
|
||||
} else {
|
||||
className = 'current-video-quality';
|
||||
icon = IconPerformance;
|
||||
tooltipKey = 'videoStatus.performanceSettings';
|
||||
}
|
||||
|
||||
const onClick = () => dispatch(openDialog(VideoQualityDialog));
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
content = { t(tooltipKey) }
|
||||
position = { 'bottom' }>
|
||||
<Label
|
||||
accessibilityText = { t(tooltipKey) }
|
||||
className = { className }
|
||||
color = { COLORS.white }
|
||||
icon = { icon }
|
||||
iconColor = '#fff'
|
||||
id = 'videoResolutionLabel'
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick = { onClick }
|
||||
text = { labelContent } />
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated {@code VideoQualityLabel}'s
|
||||
* props.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _audioOnly: boolean,
|
||||
* _visible: boolean
|
||||
* }}
|
||||
* @returns {JSX}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
return {
|
||||
..._abstractMapStateToProps(state),
|
||||
_visible: !(shouldDisplayTileView(state) || interfaceConfig.VIDEO_QUALITY_LABEL_DISABLED)
|
||||
};
|
||||
}
|
||||
const VideoQualityLabel = () => {
|
||||
const _audioOnly = useSelector((state: IReduxState) => state['features/base/audio-only'].enabled);
|
||||
const _visible = useSelector((state: IReduxState) => !(shouldDisplayTileView(state)
|
||||
|| interfaceConfig.VIDEO_QUALITY_LABEL_DISABLED));
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
|
||||
export default translate(connect(_mapStateToProps)(VideoQualityLabel));
|
||||
if (!_visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let className, icon, labelContent, tooltipKey;
|
||||
|
||||
if (_audioOnly) {
|
||||
className = 'audio-only';
|
||||
labelContent = t('videoStatus.audioOnly');
|
||||
tooltipKey = 'videoStatus.labelTooltipAudioOnly';
|
||||
} else {
|
||||
className = 'current-video-quality';
|
||||
icon = IconPerformance;
|
||||
tooltipKey = 'videoStatus.performanceSettings';
|
||||
}
|
||||
|
||||
const onClick = () => dispatch(openDialog(VideoQualityDialog));
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
content = { t(tooltipKey) }
|
||||
position = { 'bottom' }>
|
||||
<Label
|
||||
accessibilityText = { t(tooltipKey) }
|
||||
className = { className }
|
||||
color = { COLORS.white }
|
||||
icon = { icon }
|
||||
iconColor = '#fff'
|
||||
id = 'videoResolutionLabel'
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick = { onClick }
|
||||
text = { labelContent } />
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export default VideoQualityLabel;
|
||||
|
||||
61
resources/prosody-plugins/mod_auth_jitsi-shared-secret.lua
Normal file
61
resources/prosody-plugins/mod_auth_jitsi-shared-secret.lua
Normal file
@@ -0,0 +1,61 @@
|
||||
-- Authentication with shared secret where the username is ignored
|
||||
-- Copyright (C) 2023-present 8x8, Inc.
|
||||
|
||||
local new_sasl = require "util.sasl".new;
|
||||
local saslprep = require "util.encodings".stringprep.saslprep;
|
||||
local secure_equals = require "util.hashes".equals;
|
||||
|
||||
local shared_secret = module:get_option_string('shared_secret');
|
||||
if shared_secret == nil then
|
||||
module:log('error', 'No shared_secret specified. No secret to operate on!');
|
||||
return;
|
||||
end
|
||||
|
||||
module:depends("jitsi_session");
|
||||
|
||||
-- define auth provider
|
||||
local provider = {};
|
||||
|
||||
function provider.test_password(username, password)
|
||||
password = saslprep(password);
|
||||
if not password then
|
||||
return nil, "Password fails SASLprep.";
|
||||
end
|
||||
|
||||
if secure_equals(password, saslprep(shared_secret)) then
|
||||
return true;
|
||||
else
|
||||
return nil, "Auth failed. Invalid username or password.";
|
||||
end
|
||||
end
|
||||
|
||||
function provider.get_password(username)
|
||||
return shared_secret;
|
||||
end
|
||||
|
||||
function provider.set_password(username, password)
|
||||
return nil, "Set password not supported";
|
||||
end
|
||||
|
||||
function provider.user_exists(username)
|
||||
return true; -- all usernames exist
|
||||
end
|
||||
|
||||
function provider.create_user(username, password)
|
||||
return nil;
|
||||
end
|
||||
|
||||
function provider.delete_user(username)
|
||||
return nil;
|
||||
end
|
||||
|
||||
function provider.get_sasl_handler(session)
|
||||
local getpass_authentication_profile = {
|
||||
plain = function(_, username, realm)
|
||||
return shared_secret, true;
|
||||
end
|
||||
};
|
||||
return new_sasl(module.host, getpass_authentication_profile);
|
||||
end
|
||||
|
||||
module:provides("auth", provider);
|
||||
Reference in New Issue
Block a user