Compare commits

...

6 Commits

Author SHA1 Message Date
Robert Pintilii
961a9236fd ref: remove some Abstract components (#13553) 2023-07-13 12:27:34 +03:00
damencho
364e63da14 feat: New prosody auth module using shared secret.
The same as username and password but ignoring the username. Useful for jigasi and jibri where the instances can use different usernames, but the same shared secret/password.
2023-07-12 12:39:00 -05:00
Calinteodor
27c62b3d78 sdk(react-native-sdk): error fixes (#13549)
* feat(mobile/background/react-native-sdk): replaced removeListener deprecated method and fixed some undefined errors
2023-07-12 17:28:30 +03:00
Robert Pintilii
51623b47f0 ref(CSS): Cleanup (#13554)
Remove unused styles
2023-07-12 15:51:56 +03:00
Robert Pintilii
824cfc0c9c ref(chat): Refactor Chat components (#13550)
Remove Abstract component
Convert web component to function component
2023-07-12 15:51:38 +03:00
Saúl Ibarra Corretgé
398e170e2d fix(settings) fix when devices tab is not visible
Fixes: https://github.com/jitsi/jitsi-meet/issues/13461
2023-07-11 21:47:57 +02:00
48 changed files with 678 additions and 994 deletions

View File

@@ -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.
*/

View File

@@ -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;
}
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -75,6 +75,3 @@
margin-bottom: 36px;
width: 100%;
}
.navigate-section-list-empty {
text-align: center;
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -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;

View File

@@ -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
};
}

View File

@@ -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.

View File

@@ -1,6 +1,6 @@
import { Component } from 'react';
import { IMessage } from '../reducer';
import { IMessage } from '../types';
export interface IProps {

View File

@@ -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) => {

View File

@@ -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';

View File

@@ -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';

View File

@@ -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));

View File

@@ -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';

View File

@@ -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

View File

@@ -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;

View 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'];
}

View File

@@ -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;
}
}

View File

@@ -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()}.
*

View File

@@ -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);
}
/**

View File

@@ -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;
};
}

View 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;
}

View File

@@ -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
};
}

View File

@@ -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));

View File

@@ -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)
};
}

View File

@@ -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);

View File

@@ -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';

View File

@@ -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

View File

@@ -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
};
}

View File

@@ -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;
}

View File

@@ -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:

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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
};
},

View File

@@ -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;
}
}

View File

@@ -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
};
}

View File

@@ -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));

View File

@@ -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;

View File

@@ -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
};
}

View File

@@ -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));

View File

@@ -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;

View 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);