Compare commits

...

8 Commits

Author SHA1 Message Date
damencho
43be4324af fix: Fix room locking without visitors. 2023-05-02 06:25:37 -05:00
Saúl Ibarra Corretgé
c33baf4c96 fix(ios) avoid rejecting builds in progress in TestFlight 2023-05-02 10:21:30 +02:00
Saúl Ibarra Corretgé
f95a356025 feat(android) bump minimum API level to 24
Some of our dependencies (most notably WebRTC) have dropped it and we
can no longer claim to support API level 23).
2023-05-02 10:19:19 +02:00
Robert Pintilii
1ba7765898 ref(TS) Convert some native components to TS (#13281)
Remove some @ts-ignores
2023-05-02 11:09:38 +03:00
Robert Pintilii
0346fca434 fix(checkbox) Fix misalign when label has multiple lines (#13304) 2023-05-02 10:23:04 +03:00
Robert Pintilii
d267b2499d fix(chat) Fix name overflows chat bubble (#13303)
Revert color change of scroll corner
2023-05-02 10:22:49 +03:00
Дамян Минков
e3e5f1fbfa feat(visitors): Handles locked rooms for visitors. (#13296)
* feat(visitors): Handles locked rooms for visitors.

* squash: Handle locked room password on promotion.

* squash: quotes.

* squash: Renames main_domain to local_domain.

* squash: Renames fmuc_main_domain to main_domain.

Adds required config to point to the main virtual host of the main prosody. There are cases when the first visitor tries to join and there are not main participants as they are in the queue waiting for the vnode connect message and we cannot get dynamically the main domain.

* squash: Fix check for main_domain config.
2023-05-01 17:16:16 -05:00
robertpin
4697192b43 fix(keyboard-a11y) Remove space from click trigger 2023-05-01 16:50:23 +02:00
80 changed files with 671 additions and 688 deletions

View File

@@ -19,7 +19,7 @@ buildscript {
ext {
buildToolsVersion = "31.0.0"
compileSdkVersion = 32
minSdkVersion = 23
minSdkVersion = 24
targetSdkVersion = 32
supportLibVersion = "28.0.0"

View File

@@ -74,10 +74,6 @@
a:active {
color: black;
}
&::-webkit-scrollbar-corner {
background: #3a3a3a;
}
}

View File

@@ -98,7 +98,6 @@ platform :ios do
demo_account_required: false,
distribute_external: true,
groups: ENV["JITSI_BETA_TESTING_GROUPS"],
reject_build_waiting_for_review: true,
uses_non_exempt_encryption: false
)

View File

@@ -36,7 +36,7 @@ type Props = {
/**
* Function to render a bottom sheet footer element, if necessary.
*/
renderFooter?: Function;
renderFooter?: () => React.ReactNode;
/**
* Function to render a bottom sheet header element, if necessary.

View File

@@ -18,7 +18,7 @@ interface IProps extends AbstractProps, WithTranslation {
/**
* The dialog descriptionKey.
*/
descriptionKey: string;
descriptionKey?: string;
/**
* An optional initial value to initiate the field with.

View File

@@ -43,7 +43,7 @@ export default class ToolboxItem extends AbstractToolboxItem<IProps> {
* @returns {void}
*/
_onKeyPress(event?: React.KeyboardEvent) {
if (event?.key === 'Enter' || event?.key === ' ') {
if (event?.key === 'Enter') {
event.preventDefault();
this.props.onClick();
}

View File

@@ -70,7 +70,7 @@ const useStyles = makeStyles()(theme => {
'& input[type="checkbox"]': {
appearance: 'none',
backgroundColor: 'transparent',
margin: 0,
margin: '3px',
font: 'inherit',
color: theme.palette.icon03,
width: '18px',

View File

@@ -1,6 +1,5 @@
/* eslint-disable react/no-multi-comp */
import { useIsFocused } from '@react-navigation/native';
import { Route, useIsFocused } from '@react-navigation/native';
import React, { useEffect } from 'react';
import { connect } from 'react-redux';
@@ -9,7 +8,7 @@ import JitsiScreen from '../../../base/modal/components/JitsiScreen';
import { TabBarLabelCounter } from '../../../mobile/navigation/components/TabBarLabelCounter';
import { closeChat } from '../../actions.native';
import AbstractChat, {
type Props as AbstractProps,
IProps as AbstractProps,
_mapStateToProps
} from '../AbstractChat';
@@ -18,24 +17,24 @@ import MessageContainer from './MessageContainer';
import MessageRecipient from './MessageRecipient';
import styles from './styles';
type Props = AbstractProps & {
interface IProps extends AbstractProps {
/**
* Default prop for navigating between screen components(React Navigation).
*/
navigation: Object,
navigation: any;
/**
* Default prop for navigating between screen components(React Navigation).
*/
route: Object
};
route: Route<'', { privateMessageRecipient: { name: string; }; }>;
}
/**
* Implements a React native component that renders the chat window (modal) of
* the mobile client.
*/
class Chat extends AbstractChat<Props> {
class Chat extends AbstractChat<IProps> {
/**
* Implements React's {@link Component#render()}.
*
@@ -51,17 +50,16 @@ class Chat extends AbstractChat<Props> {
hasBottomTextInput = { true }
hasTabNavigator = { true }
style = { styles.chatContainer }>
{/* @ts-ignore */}
<MessageContainer messages = { _messages } />
<MessageRecipient privateMessageRecipient = { privateMessageRecipient } />
<ChatInputBar onSend = { this._onSendMessage } />
</JitsiScreen>
);
}
_onSendMessage: (string) => void;
}
export default translate(connect(_mapStateToProps)(props => {
export default translate(connect(_mapStateToProps)((props: IProps) => {
const { _nbUnreadMessages, dispatch, navigation, t } = props;
const unreadMessagesNr = _nbUnreadMessages > 0;
@@ -78,7 +76,9 @@ export default translate(connect(_mapStateToProps)(props => {
)
});
return () => isFocused && dispatch(closeChat());
return () => {
isFocused && dispatch(closeChat());
};
}, [ isFocused, _nbUnreadMessages ]);
return (

View File

@@ -1,5 +1,6 @@
import { connect } from 'react-redux';
import { IReduxState } from '../../../app/types';
import { CHAT_ENABLED } from '../../../base/flags/constants';
import { getFeatureFlag } from '../../../base/flags/functions';
import { translate } from '../../../base/i18n/functions';
@@ -10,23 +11,23 @@ import { screen } from '../../../mobile/navigation/routes';
import { getUnreadPollCount } from '../../../polls/functions';
import { getUnreadCount } from '../../functions';
type Props = AbstractButtonProps & {
interface IProps extends AbstractButtonProps {
/**
* True if the polls feature is disabled.
*/
_isPollsDisabled: boolean,
_isPollsDisabled?: boolean;
/**
* The unread message count.
*/
_unreadMessageCount: number
};
_unreadMessageCount: number;
}
/**
* Implements an {@link AbstractButton} to open the chat screen on mobile.
*/
class ChatButton extends AbstractButton<Props, *> {
class ChatButton extends AbstractButton<IProps> {
accessibilityLabel = 'toolbar.accessibilityLabel.chat';
icon = IconMessage;
label = 'toolbar.chat';
@@ -60,9 +61,9 @@ class ChatButton extends AbstractButton<Props, *> {
*
* @param {Object} state - The Redux state.
* @param {Object} ownProps - The properties explicitly passed to the component instance.
* @returns {Props}
* @returns {IProps}
*/
function _mapStateToProps(state, ownProps) {
function _mapStateToProps(state: IReduxState, ownProps: any) {
const enabled = getFeatureFlag(state, CHAT_ENABLED, true);
const { disablePolls } = state['features/base/config'];
const { visible = enabled } = ownProps;

View File

@@ -25,10 +25,6 @@ class ChatPrivacyDialog extends AbstractChatPrivacyDialog {
onSubmit = { this._onSendPrivateMessage } />
);
}
_onSendGroupMessage: () => boolean;
_onSendPrivateMessage: () => boolean;
}
export default translate(connect(_mapStateToProps, _mapDispatchToProps)(ChatPrivacyDialog));

View File

@@ -1,63 +1,65 @@
import React from 'react';
import { Text, TouchableHighlight, View } from 'react-native';
import { Text, TouchableHighlight, View, ViewStyle } from 'react-native';
import { connect } from 'react-redux';
import { IReduxState, IStore } from '../../../app/types';
import { translate } from '../../../base/i18n/functions';
import Icon from '../../../base/icons/components/Icon';
import { IconCloseLarge } from '../../../base/icons/svg';
import { ILocalParticipant } from '../../../base/participants/types';
import {
setParams
} from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
import { setLobbyChatActiveState, setPrivateMessageRecipient } from '../../actions.any';
import AbstractMessageRecipient, {
type Props as AbstractProps
IProps as AbstractProps
} from '../AbstractMessageRecipient';
import styles from './styles';
type Props = AbstractProps & {
interface IProps extends AbstractProps {
/**
* The Redux dispatch function.
*/
dispatch: Function,
dispatch: IStore['dispatch'];
/**
* Is lobby messaging active.
*/
isLobbyChatActive: boolean,
isLobbyChatActive: boolean;
/**
* The participant string for lobby chat messaging.
*/
lobbyMessageRecipient: Object,
lobbyMessageRecipient?: {
id: string;
name: string;
} | ILocalParticipant;
/**
* The participant object set for private messaging.
*/
privateMessageRecipient: Object,
};
privateMessageRecipient: { name: string; };
}
/**
* Class to implement the displaying of the recipient of the next message.
*/
class MessageRecipient extends AbstractMessageRecipient<Props> {
class MessageRecipient extends AbstractMessageRecipient<IProps> {
/**
* Constructor of the component.
*
* @param {Props} props - The props of the component.
* @param {IProps} props - The props of the component.
*/
constructor(props: Props) {
constructor(props: IProps) {
super(props);
this._onResetPrivateMessageRecipient = this._onResetPrivateMessageRecipient.bind(this);
this._onResetLobbyMessageRecipient = this._onResetLobbyMessageRecipient.bind(this);
}
_onResetLobbyMessageRecipient: () => void;
/**
* Resets lobby message recipient from state.
*
@@ -69,8 +71,6 @@ class MessageRecipient extends AbstractMessageRecipient<Props> {
dispatch(setLobbyChatActiveState(false));
}
_onResetPrivateMessageRecipient: () => void;
/**
* Resets private message recipient from state.
*
@@ -102,10 +102,10 @@ class MessageRecipient extends AbstractMessageRecipient<Props> {
if (isLobbyChatActive) {
return (
<View style = { styles.lobbyMessageRecipientContainer }>
<View style = { styles.lobbyMessageRecipientContainer as ViewStyle }>
<Text style = { styles.messageRecipientText }>
{ t('chat.lobbyChatMessageTo', {
recipient: lobbyMessageRecipient.name
recipient: lobbyMessageRecipient?.name
}) }
</Text>
<TouchableHighlight
@@ -123,7 +123,7 @@ class MessageRecipient extends AbstractMessageRecipient<Props> {
}
return (
<View style = { styles.messageRecipientContainer }>
<View style = { styles.messageRecipientContainer as ViewStyle }>
<Text style = { styles.messageRecipientText }>
{ t('chat.messageTo', {
recipient: privateMessageRecipient.name
@@ -145,9 +145,10 @@ class MessageRecipient extends AbstractMessageRecipient<Props> {
* Maps part of the redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @returns {Props}
* @param {any} _ownProps - Component's own props.
* @returns {IProps}
*/
function _mapStateToProps(state) {
function _mapStateToProps(state: IReduxState, _ownProps: any) {
const { lobbyMessageRecipient, isLobbyChatActive } = state['features/chat'];
return {

View File

@@ -23,17 +23,21 @@ interface IProps extends AbstractProps {
const styles = (theme: Theme) => {
return {
chatMessageWrapper: {
maxWidth: '100%'
maxWidth: '100%',
'&.remote': {
maxWidth: 'calc(100% - 40px)' // 100% - avatar and margin
}
},
chatMessage: {
display: 'inline-flex',
padding: '12px',
marginRight: '12px',
backgroundColor: theme.palette.ui02,
borderRadius: '4px 12px 12px 12px',
maxWidth: '100%',
marginTop: '4px',
boxSizing: 'border-box' as const,
'&.privatemessage': {
backgroundColor: theme.palette.support05
@@ -62,7 +66,8 @@ const styles = (theme: Theme) => {
replyWrapper: {
display: 'flex',
flexDirection: 'row' as const,
alignItems: 'center'
alignItems: 'center',
maxWidth: '100%'
},
messageContent: {
@@ -126,7 +131,7 @@ class ChatMessage extends AbstractChatMessage<IProps> {
return (
<div
className = { classes.chatMessageWrapper }
className = { clsx(classes.chatMessageWrapper, type) }
id = { this.props.message.messageId }
tabIndex = { -1 }>
<div

View File

@@ -28,6 +28,13 @@ const useStyles = makeStyles()(theme => {
color: theme.palette.text01
},
text: {
maxWidth: 'calc(100% - 30px)',
overflow: 'hidden',
whiteSpace: 'break-spaces',
wordBreak: 'break-all'
},
iconButton: {
padding: '2px',
@@ -72,7 +79,7 @@ const MessageRecipient = ({
className = { classes.container }
id = 'chat-recipient'
role = 'alert'>
<span>
<span className = { classes.text }>
{t(_isLobbyChatActive ? 'chat.lobbyChatMessageTo' : 'chat.messageTo', {
recipient: _isLobbyChatActive ? _lobbyMessageRecipient : _privateMessageRecipient
})}

View File

@@ -9,7 +9,7 @@ import { translate } from '../../../../base/i18n/functions';
import { navigate }
from '../../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
import { screen } from '../../../../mobile/navigation/routes';
import { IProps } from '../../LiveStream/AbstractStartLiveStreamDialog';
import { IProps, _mapStateToProps as abstractMapStateToProps } from '../../LiveStream/AbstractStartLiveStreamDialog';
import AbstractRecordButton, {
IProps as AbstractProps,
_mapStateToProps as _abstractMapStateToProps
@@ -58,6 +58,7 @@ export function mapStateToProps(state: IReduxState) {
return {
...abstractProps,
...abstractMapStateToProps(state),
visible: enabled && iosEnabled && abstractProps.visible
};
}

View File

@@ -1,8 +1,6 @@
// @flow
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Text, TouchableHighlight, View } from 'react-native';
import { GestureResponderEvent, Text, TextStyle, TouchableHighlight, View, ViewStyle } from 'react-native';
import Icon from '../../../base/icons/components/Icon';
import { RECORD_TYPE } from '../../constants';
@@ -12,69 +10,71 @@ import styles from './styles';
/**
* The type of the React {@code Component} props of {@link RecordItem}.
*/
type Props = {
interface IProps {
/**
* The id of the record.
*/
id: String,
id?: string;
/**
* The name of the record.
*/
name: String,
name?: string;
/**
* The handler for the click event.
*/
onClick: Function,
onClick?: (e?: GestureResponderEvent | React.MouseEvent) => void;
/**
* The type of the record.
*/
type: String
type?: string;
}
/**
* Component to render Record data.
*
* @param {Props} props - The props of the component.
* @param {IProps} props - The props of the component.
* @returns {React$Element<any>}
*/
export const RecordItem = ({
id,
name,
type,
/* eslint-disable-next-line no-empty-function */
/* eslint-disable-next-line @typescript-eslint/no-empty-function */
onClick = () => {}
}: Props) => {
}: IProps) => {
const { t } = useTranslation();
const IconRecord = RECORD_TYPE[type].icon;
const IconRecord = RECORD_TYPE[type ?? ''].icon;
return (
<TouchableHighlight onPress = { onClick }>
<View
key = { `record-${id}` }
style = { styles.recordItem }
style = { styles.recordItem as ViewStyle }
// @ts-ignore
title = { name }>
<View style = { styles.recordTypeIcon }>
<View style = { styles.recordTypeIcon as ViewStyle }>
{IconRecord && (
<Icon
src = { IconRecord }
style = { styles.recordIcon } />
)}
</View>
<View style = { styles.recordDetails }>
<View style = { styles.recordDetails as ViewStyle }>
<Text
key = { name }
numberOfLines = { 1 }
style = { styles.recordName }>
style = { styles.recordName as TextStyle }>
{name}
</Text>
<Text
key = { type }
style = { styles.recordType }>
{t(RECORD_TYPE[type].label)}
{t(RECORD_TYPE[type ?? ''].label)}
</Text>
</View>
</View>

View File

@@ -1,8 +1,9 @@
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { Platform, SafeAreaView, ScrollView, Text, View } from 'react-native';
import { Platform, SafeAreaView, ScrollView, Text, View, ViewStyle } from 'react-native';
import { useSelector } from 'react-redux';
import { IReduxState } from '../../../app/types';
import { IconSearch } from '../../../base/icons/svg';
import JitsiScreen from '../../../base/modal/components/JitsiScreen';
import LoadingIndicator from '../../../base/react/components/native/LoadingIndicator';
@@ -24,7 +25,7 @@ import styles from './styles';
*/
const SalesforceLinkDialog = () => {
const { t } = useTranslation();
const { clientHeight } = useSelector(state => state['features/base/responsive-ui']);
const { clientHeight } = useSelector((state: IReduxState) => state['features/base/responsive-ui']);
const {
hasDetailsErrors,
hasRecordsErrors,
@@ -48,7 +49,7 @@ const SalesforceLinkDialog = () => {
}, [ navigate, linkMeeting ]);
const renderSpinner = () => (
<View style = { [ styles.recordsSpinner, { height: clientHeight - CONTENT_HEIGHT_OFFSET } ] }>
<View style = { [ styles.recordsSpinner, { height: clientHeight - CONTENT_HEIGHT_OFFSET } ] as ViewStyle[] }>
<LoadingIndicator />
</View>
);
@@ -63,8 +64,8 @@ const SalesforceLinkDialog = () => {
<SafeAreaView>
<ScrollView
bounces = { false }
style = { [ styles.selectedRecord, { height: clientHeight - CONTENT_HEIGHT_OFFSET } ] }>
<View style = { styles.recordInfo }>
style = { [ styles.selectedRecord, { height: clientHeight - CONTENT_HEIGHT_OFFSET } ] as ViewStyle[] }>
<View style = { styles.recordInfo as ViewStyle }>
<RecordItem { ...selectedRecord } />
{ selectedRecordOwner && <RecordItem { ...selectedRecordOwner } /> }
{ hasDetailsErrors && renderDetailsErrors() }
@@ -75,9 +76,9 @@ const SalesforceLinkDialog = () => {
<Input
customStyles = {{ container: styles.notes }}
maxLength = { NOTES_MAX_LENGTH }
minHeight = { Platform.OS === 'ios' && NOTES_LINES ? 20 * NOTES_LINES : null }
minHeight = { Platform.OS === 'ios' && NOTES_LINES ? 20 * NOTES_LINES : undefined }
multiline = { true }
numberOfLines = { Platform.OS === 'ios' ? null : NOTES_LINES }
numberOfLines = { Platform.OS === 'ios' ? undefined : NOTES_LINES }
/* eslint-disable-next-line react/jsx-no-bind */
onChange = { value => setNotes(value) }
placeholder = { t('dialog.addMeetingNote') }
@@ -87,14 +88,14 @@ const SalesforceLinkDialog = () => {
);
const renderRecordsSearch = () => (
<View style = { styles.recordsSearchContainer }>
<View style = { styles.recordsSearchContainer as ViewStyle }>
<Input
icon = { IconSearch }
maxLength = { NOTES_MAX_LENGTH }
/* eslint-disable-next-line react/jsx-no-bind */
onChange = { value => setSearchTerm(value) }
placeholder = { t('dialog.searchInSalesforce') }
value = { searchTerm } />
value = { searchTerm ?? '' } />
{(!isLoading && !hasRecordsErrors) && (
<Text style = { styles.resultLabel }>
{showSearchResults
@@ -107,7 +108,7 @@ const SalesforceLinkDialog = () => {
);
const renderNoRecords = () => showNoResults && (
<View style = { [ styles.noRecords, { height: clientHeight - CONTENT_HEIGHT_OFFSET } ] }>
<View style = { [ styles.noRecords, { height: clientHeight - CONTENT_HEIGHT_OFFSET } ] as ViewStyle[] }>
<Text style = { styles.noRecordsText }>
{t('dialog.searchResultsNotFound')}
</Text>
@@ -118,7 +119,7 @@ const SalesforceLinkDialog = () => {
);
const renderRecordsError = () => (
<View style = { [ styles.recordsError, { height: clientHeight - CONTENT_HEIGHT_OFFSET } ] }>
<View style = { [ styles.recordsError, { height: clientHeight - CONTENT_HEIGHT_OFFSET } ] as ViewStyle[] }>
<Text style = { styles.recordsErrorText }>
{t('dialog.searchResultsError')}
</Text>
@@ -143,8 +144,8 @@ const SalesforceLinkDialog = () => {
<SafeAreaView>
<ScrollView
bounces = { false }
style = { [ styles.recordList, { height: clientHeight - LIST_HEIGHT_OFFSET } ] }>
{records.map(item => (
style = { [ styles.recordList, { height: clientHeight - LIST_HEIGHT_OFFSET } ] as ViewStyle[] }>
{records.map((item: any) => (
<RecordItem
key = { `record-${item.id}` }
/* eslint-disable-next-line react/jsx-no-bind */
@@ -164,7 +165,7 @@ const SalesforceLinkDialog = () => {
</View>
{
selectedRecord
&& <View style = { styles.footer }>
&& <View>
<Button
labelKey = 'dialog.Cancel'
/* eslint-disable-next-line react/jsx-no-bind */

View File

@@ -1,5 +1,6 @@
import { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { GestureResponderEvent } from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import { IReduxState } from '../app/types';
@@ -21,7 +22,7 @@ import {
interface ISelectedRecord {
id: string;
name: string;
onClick: (e?: React.MouseEvent) => void;
onClick: (e?: React.MouseEvent | GestureResponderEvent) => void;
type: string;
}

View File

@@ -1,11 +1,14 @@
import React, { PureComponent } from 'react';
import {
Text,
View
TextStyle,
View,
ViewStyle
} from 'react-native';
import { connect } from 'react-redux';
import type { Dispatch } from 'redux';
import { IReduxState, IStore } from '../../../../app/types';
import { IJitsiConference } from '../../../../base/conference/reducer';
import { getSecurityUiConfig } from '../../../../base/config/functions.any';
import { MEETING_PASSWORD_ENABLED } from '../../../../base/flags/constants';
import { getFeatureFlag } from '../../../../base/flags/functions';
@@ -40,94 +43,94 @@ const _TEXT_INPUT_PROPS = {
/**
* The type of the React {@code Component} props of {@link SecurityDialog}.
*/
type Props = {
interface IProps {
/**
* The JitsiConference which requires a password.
*/
_conference: Object,
_conference?: IJitsiConference;
/**
* Whether the local user is the moderator.
*/
_isModerator: boolean,
_isModerator: boolean;
/**
* State of the lobby mode.
*/
_lobbyEnabled: boolean,
_lobbyEnabled: boolean;
/**
* Whether the lobby mode switch is available or not.
*/
_lobbyModeSwitchVisible: boolean,
_lobbyModeSwitchVisible: boolean;
/**
* The value for how the conference is locked (or undefined if not locked)
* as defined by room-lock constants.
*/
_locked: string,
_locked?: string;
/**
* Checks if the conference room is locked or not.
*/
_lockedConference: boolean,
_lockedConference: boolean;
/**
* The current known password for the JitsiConference.
*/
_password: string,
_password?: string;
/**
* Number of digits used in the room-lock password.
*/
_passwordNumberOfDigits: number,
_passwordNumberOfDigits?: number;
/**
* Whether setting a room password is available or not.
*/
_roomPasswordControls: boolean,
_roomPasswordControls: boolean;
/**
* Redux store dispatch function.
*/
dispatch: Dispatch<any>,
dispatch: IStore['dispatch'];
/**
* Invoked to obtain translated strings.
*/
t: Function
};
t: Function;
}
/**
* The type of the React {@code Component} state of {@link SecurityDialog}.
*/
type State = {
interface IState {
/**
* Password added by the participant for room lock.
*/
passwordInputValue: string,
passwordInputValue: string;
/**
* Shows an input or a message.
*/
showElement: boolean
};
showElement: boolean;
}
/**
* Component that renders the security options dialog.
*
* @returns {React$Element<any>}
*/
class SecurityDialog extends PureComponent<Props, State> {
class SecurityDialog extends PureComponent<IProps, IState> {
/**
* Instantiates a new {@code SecurityDialog}.
*
* @inheritdoc
*/
constructor(props: Props) {
constructor(props: IProps) {
super(props);
this.state = {
@@ -180,8 +183,8 @@ class SecurityDialog extends PureComponent<Props, State> {
<Text style = { styles.lobbyModeText }>
{ t('lobby.enableDialogText') }
</Text>
<View style = { styles.lobbyModeSection }>
<Text style = { styles.lobbyModeLabel } >
<View style = { styles.lobbyModeSection as ViewStyle }>
<Text style = { styles.lobbyModeLabel as TextStyle } >
{ t('lobby.toggleLabel') }
</Text>
<Switch
@@ -267,7 +270,7 @@ class SecurityDialog extends PureComponent<Props, State> {
if (_locked === LOCKED_REMOTELY) {
if (_isModerator) {
setPasswordControls = (
<View style = { styles.passwordSetRemotelyContainer }>
<View style = { styles.passwordSetRemotelyContainer as ViewStyle }>
<Text style = { styles.passwordSetRemotelyText }>
{ t('passwordSetRemotely') }
</Text>
@@ -281,7 +284,7 @@ class SecurityDialog extends PureComponent<Props, State> {
);
} else {
setPasswordControls = (
<View style = { styles.passwordSetRemotelyContainer }>
<View style = { styles.passwordSetRemotelyContainer as ViewStyle }>
<Text style = { styles.passwordSetRemotelyTextDisabled }>
{ t('passwordSetRemotely') }
</Text>
@@ -306,7 +309,7 @@ class SecurityDialog extends PureComponent<Props, State> {
<View
style = {
_locked !== LOCKED_REMOTELY
&& styles.passwordContainerControls
&& styles.passwordContainerControls as ViewStyle
}>
<View>
{ this._setRoomPasswordMessage() }
@@ -324,7 +327,7 @@ class SecurityDialog extends PureComponent<Props, State> {
* @private
*/
_setRoomPasswordMessage() {
let textInputProps = _TEXT_INPUT_PROPS;
let textInputProps: any = _TEXT_INPUT_PROPS;
const {
_isModerator,
_locked,
@@ -362,8 +365,8 @@ class SecurityDialog extends PureComponent<Props, State> {
} else if (_locked) {
if (_locked === LOCKED_LOCALLY && typeof _password !== 'undefined') {
return (
<View style = { styles.savedPasswordContainer }>
<Text style = { styles.savedPasswordLabel }>
<View style = { styles.savedPasswordContainer as ViewStyle }>
<Text style = { styles.savedPasswordLabel as TextStyle }>
{ t('info.password') }
</Text>
<Text style = { styles.savedPassword }>
@@ -376,8 +379,6 @@ class SecurityDialog extends PureComponent<Props, State> {
}
}
_onToggleLobbyMode: () => void;
/**
* Handles the enable-disable lobby mode switch.
*
@@ -394,8 +395,6 @@ class SecurityDialog extends PureComponent<Props, State> {
}
}
_onAddPassword: () => void;
/**
* Callback to be invoked when add password button is pressed.
*
@@ -431,15 +430,13 @@ class SecurityDialog extends PureComponent<Props, State> {
return true;
}
_onChangeText: string => void;
/**
* Callback to be invoked when the text in the field changes.
*
* @param {string} passwordInputValue - The value of password input.
* @returns {void}
*/
_onChangeText(passwordInputValue) {
_onChangeText(passwordInputValue: string) {
if (!this._validateInputValue(passwordInputValue)) {
return;
}
@@ -449,8 +446,6 @@ class SecurityDialog extends PureComponent<Props, State> {
});
}
_onCancel: () => void;
/**
* Cancels value typed in text input.
*
@@ -465,8 +460,6 @@ class SecurityDialog extends PureComponent<Props, State> {
this.props.dispatch(unlockRoom());
}
_onCopy: () => void;
/**
* Copies room password.
*
@@ -478,8 +471,6 @@ class SecurityDialog extends PureComponent<Props, State> {
copyText(passwordInputValue);
}
_onSubmit: () => void;
/**
* Submits value typed in text input.
*
@@ -492,7 +483,7 @@ class SecurityDialog extends PureComponent<Props, State> {
} = this.props;
const { passwordInputValue } = this.state;
dispatch(endRoomLockRequest(_conference, passwordInputValue));
_conference && dispatch(endRoomLockRequest(_conference, passwordInputValue));
}
}
@@ -500,14 +491,14 @@ class SecurityDialog extends PureComponent<Props, State> {
* Maps part of the Redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @returns {Props}
* @returns {IProps}
*/
function _mapStateToProps(state: Object): Object {
function _mapStateToProps(state: IReduxState) {
const { conference, locked, password } = state['features/base/conference'];
const { disableLobbyPassword, hideLobbyButton } = getSecurityUiConfig(state);
const { lobbyEnabled } = state['features/lobby'];
const { roomPasswordNumberOfDigits } = state['features/base/config'];
const lobbySupported = conference && conference.isLobbySupported();
const lobbySupported = conference?.isLobbySupported();
const visible = getFeatureFlag(state, MEETING_PASSWORD_ENABLED, true);
return {

View File

@@ -1,21 +1,17 @@
// @flow
import { connect } from 'react-redux';
import { translate } from '../../../../base/i18n/functions';
import { navigate } from '../../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
import { screen } from '../../../../mobile/navigation/routes';
import AbstractSecurityDialogButton, {
type Props as AbstractSecurityDialogButtonProps,
IProps as AbstractSecurityDialogButtonProps,
_mapStateToProps as _abstractMapStateToProps
} from '../AbstractSecurityDialogButton';
type Props = AbstractSecurityDialogButtonProps;
/**
* Implements an {@link AbstractSecurityDialogButton} to open the security screen.
*/
class SecurityDialogButton<P: Props, S:*> extends AbstractSecurityDialogButton<P, S> {
class SecurityDialogButton<P extends AbstractSecurityDialogButtonProps, S> extends AbstractSecurityDialogButton<P, S> {
/**
* Opens / closes the security screen.

View File

@@ -1,5 +1,3 @@
// @flow
import BaseTheme from '../../../../base/ui/components/BaseTheme.native';
/**

View File

@@ -1,7 +1,6 @@
// @flow
import React, { Component } from 'react';
import { Text, View } from 'react-native';
import { WithTranslation } from 'react-i18next';
import { Text, View, ViewStyle } from 'react-native';
import { translate } from '../../../base/i18n/functions';
@@ -10,45 +9,40 @@ import styles, { ANDROID_UNDERLINE_COLOR, PLACEHOLDER_COLOR } from './styles';
/**
* The type of the React {@code Component} props of {@link FormRow}.
*/
type Props = {
interface IProps extends WithTranslation {
/**
*
* Component's children.
*/
children: Object,
children: React.ReactElement;
/**
* Prop to decide if a row separator is to be rendered.
*/
fieldSeparator: boolean,
fieldSeparator?: boolean;
/**
* The i18n key of the text label of the form field.
*/
label: string,
label: string;
/**
* One of 'row' (default) or 'column'.
*/
layout: string,
/**
* Invoked to obtain translated strings.
*/
t: Function
layout?: string;
}
/**
* Implements a React {@code Component} which renders a standardized row on a
* form. The component should have exactly one child component.
*/
class FormRow extends Component<Props> {
class FormRow extends Component<IProps> {
/**
* Initializes a new {@code FormRow} instance.
*
* @param {Object} props - Component properties.
*/
constructor(props) {
constructor(props: IProps) {
super(props);
React.Children.only(this.props.children);
@@ -77,7 +71,7 @@ class FormRow extends Component<Props> {
return (
<View
style = { this._getRowStyle() } >
<View style = { styles.fieldLabelContainer } >
<View style = { styles.fieldLabelContainer as ViewStyle } >
<Text
style = { [
styles.text,
@@ -87,15 +81,13 @@ class FormRow extends Component<Props> {
{ t(this.props.label) }
</Text>
</View>
<View style = { styles.fieldValueContainer } >
<View style = { styles.fieldValueContainer as ViewStyle } >
{ newChild }
</View>
</View>
);
}
_getDefaultFieldProps: (field: Component<*, *>) => Object;
/**
* Assembles the default props to the field child component of this form
* row.
@@ -108,8 +100,8 @@ class FormRow extends Component<Props> {
* @private
* @returns {Object}
*/
_getDefaultFieldProps(field: Object) {
if (field && field.type) {
_getDefaultFieldProps(field?: React.ReactElement) {
if (field?.type) { // @ts-ignore
switch (field.type.displayName) {
case 'TextInput':
return {
@@ -126,8 +118,6 @@ class FormRow extends Component<Props> {
return {};
}
_getRowStyle: () => Array<Object>;
/**
* Assembles the row style array based on the row's props.
*
@@ -136,8 +126,8 @@ class FormRow extends Component<Props> {
*/
_getRowStyle() {
const { fieldSeparator, layout } = this.props;
const rowStyle = [
styles.fieldContainer
const rowStyle: ViewStyle[] = [
styles.fieldContainer as ViewStyle
];
if (fieldSeparator) {
@@ -146,7 +136,7 @@ class FormRow extends Component<Props> {
if (layout === 'column') {
rowStyle.push(
styles.fieldContainerColumn
styles.fieldContainerColumn as ViewStyle
);
}

View File

@@ -4,7 +4,6 @@ import { Text, View } from 'react-native';
import { translate } from '../../../base/i18n/functions';
// @ts-ignore
import styles from './styles';

View File

@@ -29,7 +29,6 @@ import { BUTTON_TYPES } from '../../../base/ui/constants.any';
import { AVATAR_SIZE } from '../../../welcome/components/styles';
import { isServerURLChangeEnabled, normalizeUserInputURL } from '../../functions.native';
// @ts-ignore
import FormRow from './FormRow';
import FormSection from './FormSection';
import styles from './styles';

View File

@@ -1,8 +1,6 @@
// @flow
import { connect } from 'react-redux';
import type { Dispatch } from 'redux';
import { IReduxState } from '../../../app/types';
import { VIDEO_SHARE_BUTTON_ENABLED } from '../../../base/flags/constants';
import { getFeatureFlag } from '../../../base/flags/functions';
import { translate } from '../../../base/i18n/functions';
@@ -15,30 +13,25 @@ import { isSharingStatus } from '../../functions';
/**
* The type of the React {@code Component} props of {@link TileViewButton}.
*/
type Props = AbstractButtonProps & {
interface IProps extends AbstractButtonProps {
/**
* Whether or not the button is disabled.
*/
_isDisabled: boolean,
_isDisabled: boolean;
/**
* Whether or not the local participant is sharing a video.
*/
_sharingVideo: boolean,
/**
* The redux {@code dispatch} function.
*/
dispatch: Dispatch<any>
};
_sharingVideo: boolean;
}
/**
* Component that renders a toolbar button for toggling the tile layout view.
*
* @augments AbstractButton
*/
class VideoShareButton extends AbstractButton<Props, *> {
class VideoShareButton extends AbstractButton<IProps> {
accessibilityLabel = 'toolbar.accessibilityLabel.sharedvideo';
icon = IconPlay;
label = 'toolbar.sharedvideo';
@@ -94,17 +87,17 @@ class VideoShareButton extends AbstractButton<Props, *> {
* @param {Object} state - The Redux state.
* @param {Object} ownProps - The properties explicitly passed to the component instance.
* @private
* @returns {Props}
* @returns {IProps}
*/
function _mapStateToProps(state, ownProps): Object {
function _mapStateToProps(state: IReduxState, ownProps: any) {
const { ownerId, status: sharedVideoStatus } = state['features/shared-video'];
const localParticipantId = getLocalParticipant(state).id;
const localParticipantId = getLocalParticipant(state)?.id;
const enabled = getFeatureFlag(state, VIDEO_SHARE_BUTTON_ENABLED, true);
const { visible = enabled } = ownProps;
if (ownerId !== localParticipantId) {
return {
_isDisabled: isSharingStatus(sharedVideoStatus),
_isDisabled: isSharingStatus(sharedVideoStatus ?? ''),
_sharingVideo: false,
visible
};
@@ -112,7 +105,7 @@ function _mapStateToProps(state, ownProps): Object {
return {
_isDisabled: false,
_sharingVideo: isSharingStatus(sharedVideoStatus),
_sharingVideo: isSharingStatus(sharedVideoStatus ?? ''),
visible
};
}

View File

@@ -1,22 +1,24 @@
// @flow
import React from 'react';
import { connect } from 'react-redux';
import InputDialog from '../../../base/dialog/components/native/InputDialog';
import { translate } from '../../../base/i18n/functions';
import AbstractSharedVideoDialog from '../AbstractSharedVideoDialog';
import AbstractSharedVideoDialog, { IProps } from '../AbstractSharedVideoDialog';
interface IState {
error: boolean;
}
/**
* Implements a component to render a display name prompt.
*/
class SharedVideoDialog extends AbstractSharedVideoDialog<*> {
class SharedVideoDialog extends AbstractSharedVideoDialog<IState> {
/**
* Instantiates a new component.
*
* @inheritdoc
*/
constructor(props) {
constructor(props: IProps) {
super(props);
this.state = {
@@ -26,15 +28,13 @@ class SharedVideoDialog extends AbstractSharedVideoDialog<*> {
this._onSubmitValue = this._onSubmitValue.bind(this);
}
_onSubmitValue: () => boolean;
/**
* Callback to be invoked when the value of the link input is submitted.
*
* @param {string} value - The entered video link.
* @returns {boolean}
*/
_onSubmitValue(value) {
_onSubmitValue(value: string) {
const result = super._onSetVideoLink(value);
if (!result) {

View File

@@ -2,8 +2,7 @@ import React, { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import JitsiScreen from '../../../base/modal/components/JitsiScreen';
import { resetSearchCriteria } from '../../actions';
import { resetSearchCriteria } from '../../actions.native';
import SpeakerStatsList from './SpeakerStatsList';
import SpeakerStatsSearch from './SpeakerStatsSearch';

View File

@@ -1,9 +1,8 @@
// @flow
import { connect } from 'react-redux';
import { createToolbarEvent } from '../../../analytics/AnalyticsEvents';
import { sendAnalytics } from '../../../analytics/functions';
import { IReduxState } from '../../../app/types';
import { SPEAKERSTATS_ENABLED } from '../../../base/flags/constants';
import { getFeatureFlag } from '../../../base/flags/functions';
import { translate } from '../../../base/i18n/functions';
@@ -39,7 +38,7 @@ class SpeakerStatsButton extends AbstractSpeakerStatsButton {
* visible: boolean
* }}
*/
function _mapStateToProps(state): Object {
function _mapStateToProps(state: IReduxState) {
const enabled = getFeatureFlag(state, SPEAKERSTATS_ENABLED, true);
return {

View File

@@ -1,7 +1,5 @@
// @flow
import React from 'react';
import { Text, View } from 'react-native';
import { Text, View, ViewStyle } from 'react-native';
import Avatar from '../../../base/avatar/components/Avatar';
import StatelessAvatar from '../../../base/avatar/components/native/StatelessAvatar';
@@ -11,46 +9,44 @@ import BaseTheme from '../../../base/ui/components/BaseTheme.native';
import TimeElapsed from './TimeElapsed';
import style from './styles';
type Props = {
interface IProps {
/**
* The name of the participant.
*/
displayName: string,
displayName: string;
/**
* The total milliseconds the participant has been dominant speaker.
*/
dominantSpeakerTime: number,
/**
* The id of the user.
*/
participantId: string,
dominantSpeakerTime: number;
/**
* True if the participant is no longer in the meeting.
*/
hasLeft: boolean,
hasLeft: boolean;
/**
* True if the participant is currently the dominant speaker.
*/
isDominantSpeaker: boolean
};
isDominantSpeaker: boolean;
const SpeakerStatsItem = (props: Props) =>
/**
* The id of the user.
*/
participantId: string;
}
const SpeakerStatsItem = (props: IProps) =>
(
<View
key = { props.participantId }
style = { style.speakerStatsItemContainer }>
style = { style.speakerStatsItemContainer as ViewStyle }>
<View style = { style.speakerStatsAvatar }>
{
props.hasLeft ? (
<StatelessAvatar
className = 'userAvatar'
color = { BaseTheme.palette.ui05 }
id = 'avatar'
initials = { getInitials(props.displayName) }
size = { BaseTheme.spacing[5] } />
) : (
@@ -61,7 +57,7 @@ const SpeakerStatsItem = (props: Props) =>
)
}
</View>
<View style = { style.speakerStatsNameTime } >
<View style = { style.speakerStatsNameTime as ViewStyle } >
<Text style = { [ style.speakerStatsText, props.hasLeft && style.speakerStatsLeft ] }>
{props.displayName}
</Text>

View File

@@ -1,5 +1,3 @@
// @flow
import React from 'react';
import { View } from 'react-native';
@@ -22,5 +20,4 @@ const SpeakerStatsList = () => {
);
};
export default SpeakerStatsList;

View File

@@ -6,12 +6,11 @@ import { useDispatch, useSelector } from 'react-redux';
import { IconSearch } from '../../../base/icons/svg';
import Input from '../../../base/ui/components/native/Input';
import { escapeRegexp } from '../../../base/util/helpers';
import { initSearch } from '../../actions';
import { initSearch } from '../../actions.native';
import { isSpeakerStatsSearchDisabled } from '../../functions';
import styles from './styles';
/**
* React component for display an individual user's speaker stats.
*

View File

@@ -1,6 +1,5 @@
/* @flow */
import React, { PureComponent } from 'react';
import { WithTranslation } from 'react-i18next';
import { Text } from 'react-native';
import { translate } from '../../../base/i18n/functions';
@@ -9,23 +8,18 @@ import { createLocalizedTime } from '../timeFunctions';
/**
* The type of the React {@code Component} props of {@link TimeElapsed}.
*/
type Props = {
interface IProps extends WithTranslation {
/**
* Style for text.
*/
style: Object,
/**
* The function to translate human-readable text.
*/
t: Function,
style: Object;
/**
* The milliseconds to be converted into a human-readable format.
*/
time: number
};
time: number;
}
/**
* React component for displaying total time elapsed. Converts a total count of
@@ -34,7 +28,7 @@ type Props = {
*
* @augments Component
*/
class TimeElapsed extends PureComponent<Props> {
class TimeElapsed extends PureComponent<IProps> {
/**
* Implements React's {@link Component#render()}.
*

View File

@@ -1,12 +1,8 @@
/* eslint-disable lines-around-comment */
import React, { ReactElement } from 'react';
import { GestureResponderEvent, StyleProp } from 'react-native';
import { connect } from 'react-redux';
// @ts-ignore
import Container from '../../../base/react/components/native/Container';
// @ts-ignore
import Text from '../../../base/react/components/native/Text';
import {
AbstractCaptions,
@@ -14,10 +10,8 @@ import {
_abstractMapStateToProps
} from '../AbstractCaptions';
// @ts-ignore
import styles from './styles';
/**
* The type of the React {@code Component} props of {@link Captions}.
*/

View File

@@ -1,5 +1,3 @@
/* eslint-disable lines-around-comment */
import { connect } from 'react-redux';
import { IReduxState } from '../../../app/types';
@@ -8,17 +6,13 @@ import { getFeatureFlag } from '../../../base/flags/functions';
import { translate } from '../../../base/i18n/functions';
import { IconSubtitles } from '../../../base/icons/svg';
import { navigate }
// @ts-ignore
from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
// @ts-ignore
import { screen } from '../../../mobile/navigation/routes';
import {
AbstractClosedCaptionButton,
IAbstractProps,
_abstractMapStateToProps
} from '../AbstractClosedCaptionButton';
/**
* A button which starts/stops the transcriptions.
*/
@@ -52,7 +46,7 @@ class ClosedCaptionButton
* @private
* @returns {Props}
*/
export function mapStateToProps(state: IReduxState, ownProps: IAbstractProps) {
export function mapStateToProps(state: IReduxState, ownProps: any) {
const enabled = getFeatureFlag(state, CLOSE_CAPTIONS_ENABLED, true);
const abstractProps = _abstractMapStateToProps(state, ownProps);

View File

@@ -1,10 +1,7 @@
/* eslint-disable lines-around-comment */
import React from 'react';
import { ScrollView } from 'react-native';
import LanguageListItem from './LanguageListItem';
// @ts-ignore
import styles from './styles';
interface ILanguageListProps {
@@ -13,7 +10,6 @@ interface ILanguageListProps {
selectedLanguage: string;
}
interface ILanguageItem {
id: string;
lang: string;
@@ -30,7 +26,6 @@ const LanguageList = ({ items, onLanguageSelected }: ILanguageListProps) => {
const listItems = items?.map(item => (
<LanguageListItem
key = { item.id }
// @ts-ignore
lang = { item.lang }
onLanguageSelected = { onLanguageSelected }
selected = { item.selected } />

View File

@@ -1,5 +1,3 @@
/* eslint-disable lines-around-comment */
import React, { useCallback } from 'react';
import { WithTranslation } from 'react-i18next';
import { StyleProp, TouchableHighlight, View, ViewStyle } from 'react-native';
@@ -9,7 +7,6 @@ import { translate } from '../../../base/i18n/functions';
import Icon from '../../../base/icons/components/Icon';
import { IconCheck } from '../../../base/icons/svg';
// @ts-ignore
import styles from './styles';

View File

@@ -1,21 +1,15 @@
/* eslint-disable lines-around-comment */
import React, { useCallback } from 'react';
// @ts-ignore
import JitsiScreen from '../../../base/modal/components/JitsiScreen';
import { goBack }
// @ts-ignore
from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
import AbstractLanguageSelectorDialog, {
IAbstractLanguageSelectorDialogProps
} from '../AbstractLanguageSelectorDialog';
import LanguageList from './LanguageList';
// @ts-ignore
import styles from './styles';
const LanguageSelectorDialog = (props: IAbstractLanguageSelectorDialogProps) => {
const { language, listItems, onLanguageSelected, subtitles } = props;
@@ -25,7 +19,6 @@ const LanguageSelectorDialog = (props: IAbstractLanguageSelectorDialogProps) =>
}, [ language ]);
return (
// @ts-ignore
<JitsiScreen
disableForcedKeyboardDismiss = { true }
style = { styles.subtitlesContainer }>

View File

@@ -1,7 +1,6 @@
// @flow
import { connect } from 'react-redux';
import { IReduxState } from '../../../app/types';
import { setAudioOnly, toggleAudioOnly } from '../../../base/audio-only/actions';
import { AUDIO_ONLY_BUTTON_ENABLED } from '../../../base/flags/constants';
import { getFeatureFlag } from '../../../base/flags/functions';
@@ -16,28 +15,23 @@ import { screen } from '../../../mobile/navigation/routes';
/**
* The type of the React {@code Component} props of {@link AudioOnlyButton}.
*/
type Props = AbstractButtonProps & {
interface IProps extends AbstractButtonProps {
/**
* Whether the current conference is in audio only mode or not.
*/
_audioOnly: boolean,
_audioOnly: boolean;
/**
* Indicates whether the car mode is enabled.
*/
_startCarMode: boolean,
/**
* The redux {@code dispatch} function.
*/
dispatch: Function
};
_startCarMode?: boolean;
}
/**
* An implementation of a button for toggling the audio-only mode.
*/
class AudioOnlyButton extends AbstractButton<Props, *> {
class AudioOnlyButton extends AbstractButton<IProps> {
accessibilityLabel = 'toolbar.accessibilityLabel.audioOnly';
icon = IconAudioOnly;
label = 'toolbar.audioOnlyOn';
@@ -86,7 +80,7 @@ class AudioOnlyButton extends AbstractButton<Props, *> {
* _audioOnly: boolean
* }}
*/
function _mapStateToProps(state, ownProps): Object {
function _mapStateToProps(state: IReduxState, ownProps: any) {
const { enabled: audioOnly } = state['features/base/audio-only'];
const enabledInFeatureFlags = getFeatureFlag(state, AUDIO_ONLY_BUTTON_ENABLED, true);
const { startCarMode } = state['features/base/settings'];

View File

@@ -1,17 +1,14 @@
/* eslint-disable lines-around-comment */
import React, { useCallback } from 'react';
import { View } from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import { createBreakoutRoomsEvent, createToolbarEvent } from '../../../analytics/AnalyticsEvents';
import { sendAnalytics } from '../../../analytics/functions';
// @ts-ignore
import { appNavigate } from '../../../app/actions';
import { IReduxState } from '../../../app/types';
import ColorSchemeRegistry from '../../../base/color-scheme/ColorSchemeRegistry';
import { endConference } from '../../../base/conference/actions';
import { hideSheet } from '../../../base/dialog/actions';
// @ts-ignore
import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
import { PARTICIPANT_ROLE } from '../../../base/participants/constants';
import { getLocalParticipant } from '../../../base/participants/functions';

View File

@@ -8,7 +8,6 @@ import { BUTTON_TYPES } from '../../../base/ui/constants.native';
import HangupMenu from './HangupMenu';
/**
* Button for showing the hangup menu.
*

View File

@@ -1,9 +1,8 @@
// @flow
import { connect } from 'react-redux';
import { createToolbarEvent } from '../../../analytics/AnalyticsEvents';
import { sendAnalytics } from '../../../analytics/functions';
import { IReduxState } from '../../../app/types';
import { translate } from '../../../base/i18n/functions';
import { IconCloudUpload } from '../../../base/icons/svg';
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
@@ -15,7 +14,7 @@ import { isSalesforceEnabled } from '../../../salesforce/functions';
/**
* Implementation of a button for opening the Salesforce link dialog.
*/
class LinkToSalesforceButton extends AbstractButton<AbstractButtonProps, *> {
class LinkToSalesforceButton extends AbstractButton<AbstractButtonProps> {
accessibilityLabel = 'toolbar.accessibilityLabel.linkToSalesforce';
icon = IconCloudUpload;
label = 'toolbar.linkToSalesforce';
@@ -40,7 +39,7 @@ class LinkToSalesforceButton extends AbstractButton<AbstractButtonProps, *> {
* @private
* @returns {Props}
*/
function mapStateToProps(state) {
function mapStateToProps(state: IReduxState) {
return {
visible: isSalesforceEnabled(state)
};

View File

@@ -1,4 +1,3 @@
/* eslint-disable lines-around-comment */
import { connect } from 'react-redux';
import { IReduxState } from '../../../app/types';
@@ -8,9 +7,7 @@ import { translate } from '../../../base/i18n/functions';
import { IconCar } from '../../../base/icons/svg';
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
import { navigate }
// @ts-ignore
from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
// @ts-ignore
import { screen } from '../../../mobile/navigation/routes';
/* eslint-enable lines-around-comment */
@@ -50,5 +47,4 @@ function _mapStateToProps(state: IReduxState, ownProps: AbstractButtonProps): Ob
};
}
// @ts-ignore
export default translate(connect(_mapStateToProps)(OpenCarmodeButton));

View File

@@ -1,9 +1,9 @@
// @flow
import React, { PureComponent } from 'react';
import { ViewStyle } from 'react-native';
import { Divider } from 'react-native-paper';
import { connect } from 'react-redux';
import { IReduxState, IStore } from '../../../app/types';
import { hideSheet } from '../../../base/dialog/actions';
import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
import { bottomSheetStyles } from '../../../base/dialog/components/native/styles';
@@ -32,58 +32,58 @@ import ScreenSharingButton from './ScreenSharingButton';
/**
* The type of the React {@code Component} props of {@link OverflowMenu}.
*/
type Props = {
interface IProps {
/**
* True if the overflow menu is currently visible, false otherwise.
*/
_isOpen: boolean,
/**
* Whether the recoding button should be enabled or not.
*/
_recordingEnabled: boolean,
/**
* The width of the screen.
*/
_width: number,
/**
* Whether or not the reactions feature is enabled.
*/
_reactionsEnabled: boolean,
/**
* Used for hiding the dialog when the selection was completed.
*/
dispatch: Function,
_isOpen: boolean;
/**
* Whether or not speaker stats is disable.
*/
_isSpeakerStatsDisabled: boolean
};
type State = {
_isSpeakerStatsDisabled?: boolean;
/**
* True if the bottom scheet is scrolled to the top.
* Whether or not the reactions feature is enabled.
*/
scrolledToTop: boolean
_reactionsEnabled: boolean;
/**
* Whether the recoding button should be enabled or not.
*/
_recordingEnabled: boolean;
/**
* The width of the screen.
*/
_width: number;
/**
* Used for hiding the dialog when the selection was completed.
*/
dispatch: IStore['dispatch'];
}
interface IState {
/**
* True if the bottom sheet is scrolled to the top.
*/
scrolledToTop: boolean;
}
/**
* Implements a React {@code Component} with some extra actions in addition to
* those in the toolbar.
*/
class OverflowMenu extends PureComponent<Props, State> {
class OverflowMenu extends PureComponent<IProps, IState> {
/**
* Initializes a new {@code OverflowMenu} instance.
*
* @inheritdoc
*/
constructor(props: Props) {
constructor(props: IProps) {
super(props);
this.state = {
@@ -105,7 +105,8 @@ class OverflowMenu extends PureComponent<Props, State> {
const {
_isSpeakerStatsDisabled,
_reactionsEnabled,
_width
_width,
dispatch
} = this.props;
const toolbarButtons = getMovableButtons(_width);
@@ -117,6 +118,7 @@ class OverflowMenu extends PureComponent<Props, State> {
const topButtonProps = {
afterClick: this._onCancel,
dispatch,
showLabel: true,
styles: {
...bottomSheetStyles.buttons,
@@ -132,21 +134,24 @@ class OverflowMenu extends PureComponent<Props, State> {
<BottomSheet
renderFooter = { _reactionsEnabled && !toolbarButtons.has('raisehand')
? this._renderReactionMenu
: null }>
: undefined }>
<OpenCarmodeButton { ...topButtonProps } />
<AudioOnlyButton { ...buttonProps } />
{!_reactionsEnabled && !toolbarButtons.has('raisehand') && <RaiseHandButton { ...buttonProps } />}
<Divider style = { styles.divider } />
{/* @ts-ignore */}
<Divider style = { styles.divider as ViewStyle } />
<SecurityDialogButton { ...buttonProps } />
<RecordButton { ...buttonProps } />
<LiveStreamButton { ...buttonProps } />
<LinkToSalesforceButton { ...buttonProps } />
<Divider style = { styles.divider } />
{/* @ts-ignore */}
<Divider style = { styles.divider as ViewStyle } />
<SharedVideoButton { ...buttonProps } />
{!toolbarButtons.has('screensharing') && <ScreenSharingButton { ...buttonProps } />}
{!_isSpeakerStatsDisabled && <SpeakerStatsButton { ...buttonProps } />}
{!toolbarButtons.has('tileview') && <TileViewButton { ...buttonProps } />}
<Divider style = { styles.divider } />
{/* @ts-ignore */}
<Divider style = { styles.divider as ViewStyle } />
<ClosedCaptionButton { ...buttonProps } />
<SharedDocumentButton { ...buttonProps } />
<SettingsButton { ...buttonProps } />
@@ -165,7 +170,7 @@ class OverflowMenu extends PureComponent<Props, State> {
}
/**
* Functoin to render the reaction menu as the footer of the bottom sheet.
* Function to render the reaction menu as the footer of the bottom sheet.
*
* @returns {React$Element}
*/
@@ -181,9 +186,9 @@ class OverflowMenu extends PureComponent<Props, State> {
*
* @param {Object} state - Redux state.
* @private
* @returns {Props}
* @returns {IProps}
*/
function _mapStateToProps(state) {
function _mapStateToProps(state: IReduxState) {
return {
_isSpeakerStatsDisabled: isSpeakerStatsDisabled(state),
_reactionsEnabled: isReactionsEnabled(state),

View File

@@ -1,5 +1,6 @@
import { connect } from 'react-redux';
import { IReduxState } from '../../../app/types';
import { openSheet } from '../../../base/dialog/actions';
import { OVERFLOW_MENU_ENABLED } from '../../../base/flags/constants';
import { getFeatureFlag } from '../../../base/flags/functions';
@@ -9,21 +10,10 @@ import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/too
import OverflowMenu from './OverflowMenu';
/**
* The type of the React {@code Component} props of {@link OverflowMenuButton}.
*/
type Props = AbstractButtonProps & {
/**
* The redux {@code dispatch} function.
*/
dispatch: Function
};
/**
* An implementation of a button for showing the {@code OverflowMenu}.
*/
class OverflowMenuButton extends AbstractButton<Props, *> {
class OverflowMenuButton extends AbstractButton<AbstractButtonProps> {
accessibilityLabel = 'toolbar.accessibilityLabel.moreActions';
icon = IconDotsHorizontal;
label = 'toolbar.moreActions';
@@ -47,7 +37,7 @@ class OverflowMenuButton extends AbstractButton<Props, *> {
* @private
* @returns {Props}
*/
function _mapStateToProps(state): Object {
function _mapStateToProps(state: IReduxState) {
const enabledFlag = getFeatureFlag(state, OVERFLOW_MENU_ENABLED, true);
return {

View File

@@ -1,10 +1,8 @@
// @flow
import { connect } from 'react-redux';
import { type Dispatch } from 'redux';
import { createToolbarEvent } from '../../../analytics/AnalyticsEvents';
import { sendAnalytics } from '../../../analytics/functions';
import { IReduxState } from '../../../app/types';
import { RAISE_HAND_ENABLED } from '../../../base/flags/constants';
import { getFeatureFlag } from '../../../base/flags/functions';
import { translate } from '../../../base/i18n/functions';
@@ -14,33 +12,29 @@ import {
getLocalParticipant,
hasRaisedHand
} from '../../../base/participants/functions';
import { ILocalParticipant } from '../../../base/participants/types';
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
/**
* The type of the React {@code Component} props of {@link RaiseHandButton}.
*/
type Props = AbstractButtonProps & {
interface IProps extends AbstractButtonProps {
/**
* The local participant.
*/
_localParticipant: Object,
_localParticipant?: ILocalParticipant;
/**
* Whether the participant raused their hand or not.
* Whether the participant raised their hand or not.
*/
_raisedHand: boolean,
/**
* The redux {@code dispatch} function.
*/
dispatch: Dispatch<any>
};
_raisedHand: boolean;
}
/**
* An implementation of a button to raise or lower hand.
*/
class RaiseHandButton extends AbstractButton<Props, *> {
class RaiseHandButton extends AbstractButton<IProps> {
accessibilityLabel = 'toolbar.accessibilityLabel.raiseHand';
icon = IconRaiseHand;
label = 'toolbar.raiseYourHand';
@@ -88,9 +82,9 @@ class RaiseHandButton extends AbstractButton<Props, *> {
* @param {Object} state - The Redux state.
* @param {Object} ownProps - The properties explicitly passed to the component instance.
* @private
* @returns {Props}
* @returns {IProps}
*/
function _mapStateToProps(state, ownProps): Object {
function _mapStateToProps(state: IReduxState, ownProps: any) {
const _localParticipant = getLocalParticipant(state);
const enabled = getFeatureFlag(state, RAISE_HAND_ENABLED, true);
const { visible = enabled } = ownProps;

View File

@@ -1,7 +1,6 @@
// @flow
import { connect } from 'react-redux';
import { IReduxState } from '../../../app/types';
import { translate } from '../../../base/i18n/functions';
import { IconCameraRefresh } from '../../../base/icons/svg';
import { toggleCameraFacingMode } from '../../../base/media/actions';
@@ -12,28 +11,23 @@ import { isLocalTrackMuted } from '../../../base/tracks/functions.native';
/**
* The type of the React {@code Component} props of {@link ToggleCameraButton}.
*/
type Props = AbstractButtonProps & {
interface IProps extends AbstractButtonProps {
/**
* Whether the current conference is in audio only mode or not.
*/
_audioOnly: boolean,
_audioOnly: boolean;
/**
* Whether video is currently muted or not.
*/
_videoMuted: boolean,
/**
* The redux {@code dispatch} function.
*/
dispatch: Function
};
_videoMuted: boolean;
}
/**
* An implementation of a button for toggling the camera facing mode.
*/
class ToggleCameraButton extends AbstractButton<Props, *> {
class ToggleCameraButton extends AbstractButton<IProps> {
accessibilityLabel = 'toolbar.accessibilityLabel.toggleCamera';
icon = IconCameraRefresh;
label = 'toolbar.toggleCamera';
@@ -72,7 +66,7 @@ class ToggleCameraButton extends AbstractButton<Props, *> {
* _videoMuted: boolean
* }}
*/
function _mapStateToProps(state): Object {
function _mapStateToProps(state: IReduxState) {
const { enabled: audioOnly } = state['features/base/audio-only'];
const tracks = state['features/base/tracks'];

View File

@@ -1,7 +1,6 @@
// @flow
import { connect } from 'react-redux';
import { IReduxState } from '../../../app/types';
import { translate } from '../../../base/i18n/functions';
import { IconAudioOnlyOff } from '../../../base/icons/svg';
import { updateSettings } from '../../../base/settings/actions';
@@ -10,23 +9,18 @@ import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/too
/**
* The type of the React {@code Component} props of {@link ToggleSelfViewButton}.
*/
type Props = AbstractButtonProps & {
interface IProps extends AbstractButtonProps {
/**
* Whether the self view is disabled or not.
*/
_disableSelfView: boolean,
/**
* The redux {@code dispatch} function.
*/
dispatch: Function
};
_disableSelfView: boolean;
}
/**
* An implementation of a button for toggling the self view.
*/
class ToggleSelfViewButton extends AbstractButton<Props, *> {
class ToggleSelfViewButton extends AbstractButton<IProps> {
accessibilityLabel = 'toolbar.accessibilityLabel.selfView';
icon = IconAudioOnlyOff;
label = 'videothumbnail.hideSelfView';
@@ -69,7 +63,7 @@ class ToggleSelfViewButton extends AbstractButton<Props, *> {
* _disableSelfView: boolean
* }}
*/
function _mapStateToProps(state): Object {
function _mapStateToProps(state: IReduxState) {
const { disableSelfView } = state['features/base/settings'];
return {

View File

@@ -1,13 +1,11 @@
// @flow
import React from 'react';
import { View } from 'react-native';
import { View, ViewStyle } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { connect } from 'react-redux';
import { IReduxState } from '../../../app/types';
import ColorSchemeRegistry from '../../../base/color-scheme/ColorSchemeRegistry';
import Platform from '../../../base/react/Platform.native';
import { StyleType } from '../../../base/styles/functions.native';
import ChatButton from '../../../chat/components/native/ChatButton';
import ReactionsMenuButton from '../../../reactions/components/native/ReactionsMenuButton';
import { isReactionsEnabled } from '../../../reactions/functions.any';
@@ -27,38 +25,38 @@ import styles from './styles';
/**
* The type of {@link Toolbox}'s React {@code Component} props.
*/
type Props = {
interface IProps {
/**
* Whether the end conference feature is supported.
*/
_endConferenceSupported: boolean,
/**
* Whether or not the reactions feature is enabled.
*/
_reactionsEnabled: boolean,
/**
* The color-schemed stylesheet of the feature.
*/
_styles: StyleType,
/**
* The indicator which determines whether the toolbox is visible.
*/
_visible: boolean,
_endConferenceSupported: boolean;
/**
* Whether we are in visitors mode.
*/
_iAmVisitor: boolean,
_iAmVisitor: boolean;
/**
* Whether or not the reactions feature is enabled.
*/
_reactionsEnabled: boolean;
/**
* The color-schemed stylesheet of the feature.
*/
_styles: any;
/**
* The indicator which determines whether the toolbox is visible.
*/
_visible: boolean;
/**
* The width of the screen.
*/
_width: number
};
_width: number;
}
/**
* Implements the conference Toolbox on React Native.
@@ -66,7 +64,7 @@ type Props = {
* @param {Object} props - The props of the component.
* @returns {React$Element}.
*/
function Toolbox(props: Props) {
function Toolbox(props: IProps) {
const { _endConferenceSupported, _reactionsEnabled, _styles, _visible, _iAmVisitor, _width } = props;
if (!_visible) {
@@ -74,7 +72,7 @@ function Toolbox(props: Props) {
}
const bottomEdge = Platform.OS === 'ios' && _visible;
const { buttonStylesBorderless, hangupButtonStyles, hangupMenuButtonStyles, toggledButtonStyles } = _styles;
const { buttonStylesBorderless, hangupButtonStyles, toggledButtonStyles } = _styles;
const additionalButtons = getMovableButtons(_width);
const backgroundToggledStyle = {
...toggledButtonStyles,
@@ -93,12 +91,14 @@ function Toolbox(props: Props) {
return (
<View
style = { styles.toolboxContainer }>
style = { styles.toolboxContainer as ViewStyle }>
<SafeAreaView
accessibilityRole = 'toolbar'
// @ts-ignore
edges = { [ bottomEdge && 'bottom' ].filter(Boolean) }
pointerEvents = 'box-none'
style = { style }>
style = { style as ViewStyle }>
{!_iAmVisitor && <AudioMuteButton
styles = { buttonStylesBorderless }
toggledStyles = { toggledButtonStyles } />
@@ -108,9 +108,9 @@ function Toolbox(props: Props) {
toggledStyles = { toggledButtonStyles } />
}
{additionalButtons.has('chat')
&& <ChatButton
styles = { buttonStylesBorderless }
toggledStyles = { backgroundToggledStyle } />
&& <ChatButton
styles = { buttonStylesBorderless }
toggledStyles = { backgroundToggledStyle } />
}
{!_iAmVisitor && additionalButtons.has('screensharing')
&& <ScreenSharingButton styles = { buttonStylesBorderless } />}
@@ -127,9 +127,7 @@ function Toolbox(props: Props) {
toggledStyles = { toggledButtonStyles } />
}
{ _endConferenceSupported
? <HangupMenuButton
styles = { hangupMenuButtonStyles }
toggledStyles = { toggledButtonStyles } />
? <HangupMenuButton />
: <HangupButton
styles = { hangupButtonStyles } />
}
@@ -145,9 +143,9 @@ function Toolbox(props: Props) {
* @param {Object} state - The redux state of which parts are to be mapped to
* {@code Toolbox} props.
* @private
* @returns {Props}
* @returns {IProps}
*/
function _mapStateToProps(state: Object): Object {
function _mapStateToProps(state: IReduxState) {
const { conference } = state['features/base/conference'];
const endConferenceSupported = conference?.isEndConferenceSupported();

View File

@@ -1,11 +1,9 @@
// @flow
import { WithTranslation } from 'react-i18next';
import { translate } from '../../base/i18n/functions';
import ExpandedLabel, { Props as AbstractProps } from '../../base/label/components/native/ExpandedLabel';
import ExpandedLabel, { IProps as AbstractProps } from '../../base/label/components/native/ExpandedLabel';
type Props = AbstractProps & {
t: Function
}
type Props = AbstractProps & WithTranslation;
/**
* A react {@code Component} that implements an expanded label as tooltip-like

View File

@@ -1,12 +1,10 @@
// @flow
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { translate } from '../../base/i18n/functions';
import Label from '../../base/label/components/native/Label';
import { type Props, _mapStateToProps } from './AbstractTranscribingLabel';
import { IProps, _mapStateToProps } from './AbstractTranscribingLabel';
/**
* React {@code Component} for displaying a label when a transcriber is in the
@@ -14,7 +12,7 @@ import { type Props, _mapStateToProps } from './AbstractTranscribingLabel';
*
* @augments Component
*/
class TranscribingLabel extends Component<Props> {
class TranscribingLabel extends Component<IProps> {
/**
* Renders the platform-specific label component.

View File

@@ -1,7 +1,6 @@
// @flow
import { connect } from 'react-redux';
import { IReduxState } from '../../../app/types';
import { approveParticipant } from '../../../av-moderation/actions';
import { isSupported } from '../../../av-moderation/functions';
import { translate } from '../../../base/i18n/functions';
@@ -11,34 +10,29 @@ import { getParticipantById, isLocalParticipantModerator } from '../../../base/p
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
import { isForceMuted } from '../../../participants-pane/functions';
export type Props = AbstractButtonProps & {
/**
* The redux {@code dispatch} function.
*/
dispatch: Function,
export interface IProps extends AbstractButtonProps {
/**
* Whether or not the participant is audio force muted.
*/
isAudioForceMuted: boolean,
isAudioForceMuted: boolean;
/**
* Whether or not the participant is video force muted.
*/
isVideoForceMuted: boolean,
isVideoForceMuted: boolean;
/**
* The ID of the participant object that this button is supposed to
* ask to unmute.
*/
participantID: string
};
participantID: string;
}
/**
* An abstract remote video menu button which asks the remote participant to unmute.
*/
class AskUnmuteButton extends AbstractButton<Props, *> {
class AskUnmuteButton extends AbstractButton<IProps> {
accessibilityLabel = 'participantsPane.actions.askUnmute';
icon = IconMic;
label = 'participantsPane.actions.askUnmute';
@@ -91,16 +85,16 @@ class AskUnmuteButton extends AbstractButton<Props, *> {
*
* @param {Object} state - The Redux state.
* @param {Object} ownProps - Properties of component.
* @returns {Props}
* @returns {IProps}
*/
function mapStateToProps(state, ownProps) {
function mapStateToProps(state: IReduxState, ownProps: any) {
const { participantID } = ownProps;
const participant = getParticipantById(state, participantID);
return {
isAudioForceMuted: isForceMuted(participant, MEDIA_TYPE.AUDIO, state),
isVideoForceMuted: isForceMuted(participant, MEDIA_TYPE.VIDEO, state),
visible: isLocalParticipantModerator(state) && isSupported()
visible: isLocalParticipantModerator(state) && isSupported()(state)
};
}

View File

@@ -7,28 +7,18 @@ import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/too
import ConnectionStatusComponent from './ConnectionStatusComponent';
export type Props = AbstractButtonProps & {
/**
* The redux {@code dispatch} function.
*/
dispatch: Function,
export interface IProps extends AbstractButtonProps {
/**
* The ID of the participant that this button is supposed to pin.
*/
participantID: string,
/**
* The function to be used to translate i18n labels.
*/
t: Function
};
participantID: string;
}
/**
* A remote video menu button which shows the connection statistics.
*/
class ConnectionStatusButton extends AbstractButton<Props, *> {
class ConnectionStatusButton extends AbstractButton<IProps> {
icon = IconInfoCircle;
label = 'videothumbnail.connectionInfo';

View File

@@ -1,5 +1,3 @@
// @flow
import { connect } from 'react-redux';
import { translate } from '../../../base/i18n/functions';

View File

@@ -1,5 +1,3 @@
// @flow
import React from 'react';
import { connect } from 'react-redux';
@@ -28,8 +26,6 @@ class GrantModeratorDialog extends AbstractGrantModeratorDialog {
onSubmit = { this._onSubmit } />
);
}
_onSubmit: () => boolean;
}
export default translate(connect(abstractMapStateToProps)(GrantModeratorDialog));

View File

@@ -1,7 +1,6 @@
// @flow
import { connect } from 'react-redux';
import { IReduxState } from '../../../app/types';
import { translate } from '../../../base/i18n/functions';
import { isLocalParticipantModerator } from '../../../base/participants/functions';
import AbstractKickButton from '../AbstractKickButton';
@@ -12,7 +11,7 @@ import AbstractKickButton from '../AbstractKickButton';
* @param {Object} state - The Redux state.
* @returns {Props}
*/
function _mapStateToProps(state) {
function _mapStateToProps(state: IReduxState) {
return {
visible: isLocalParticipantModerator(state)
};

View File

@@ -1,5 +1,3 @@
// @flow
import React from 'react';
import { connect } from 'react-redux';
@@ -29,8 +27,6 @@ class KickRemoteParticipantDialog extends AbstractKickRemoteParticipantDialog {
title = 'dialog.kickParticipantTitle' />
);
}
_onSubmit: () => boolean;
}
export default translate(connect()(KickRemoteParticipantDialog));

View File

@@ -1,7 +1,8 @@
import React, { PureComponent } from 'react';
import { Text, View } from 'react-native';
import { Text, TextStyle, View, ViewStyle } from 'react-native';
import { connect } from 'react-redux';
import { IReduxState, IStore } from '../../../app/types';
import Avatar from '../../../base/avatar/components/Avatar';
import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
import { bottomSheetStyles } from '../../../base/dialog/components/native/styles';
@@ -10,50 +11,50 @@ import {
getLocalParticipant,
getParticipantDisplayName
} from '../../../base/participants/functions';
import { ILocalParticipant } from '../../../base/participants/types';
import ToggleSelfViewButton from '../../../toolbox/components/native/ToggleSelfViewButton';
import ConnectionStatusButton from './ConnectionStatusButton';
import styles from './styles';
/**
* Size of the rendered avatar in the menu.
*/
const AVATAR_SIZE = 24;
type Props = {
interface IProps {
/**
* The local participant.
*/
_participant: Object,
_participant?: ILocalParticipant;
/**
* Display name of the participant retrieved from Redux.
*/
_participantDisplayName: string,
_participantDisplayName: string;
/**
* The Redux dispatch function.
*/
dispatch: Function,
dispatch: IStore['dispatch'];
/**
* Translation function.
*/
t: Function
t: Function;
}
/**
* Class to implement a popup menu that opens upon long pressing a thumbnail.
*/
class LocalVideoMenu extends PureComponent<Props> {
class LocalVideoMenu extends PureComponent<IProps> {
/**
* Constructor of the component.
*
* @inheritdoc
*/
constructor(props: Props) {
constructor(props: IProps) {
super(props);
this._renderMenuHeader = this._renderMenuHeader.bind(this);
@@ -68,7 +69,7 @@ class LocalVideoMenu extends PureComponent<Props> {
const { _participant } = this.props;
const buttonProps = {
showLabel: true,
participantID: _participant.id,
participantID: _participant?.id ?? '',
styles: bottomSheetStyles.buttons
};
@@ -94,11 +95,11 @@ class LocalVideoMenu extends PureComponent<Props> {
<View
style = { [
bottomSheetStyles.sheet,
styles.participantNameContainer ] }>
styles.participantNameContainer ] as ViewStyle[] }>
<Avatar
participantId = { _participant.id }
participantId = { _participant?.id }
size = { AVATAR_SIZE } />
<Text style = { styles.participantNameLabel }>
<Text style = { styles.participantNameLabel as TextStyle }>
{ this.props._participantDisplayName }
</Text>
</View>
@@ -111,14 +112,14 @@ class LocalVideoMenu extends PureComponent<Props> {
*
* @param {Object} state - Redux state.
* @private
* @returns {Props}
* @returns {IProps}
*/
function _mapStateToProps(state) {
function _mapStateToProps(state: IReduxState) {
const participant = getLocalParticipant(state);
return {
_participant: participant,
_participantDisplayName: getParticipantDisplayName(state, participant.id)
_participantDisplayName: getParticipantDisplayName(state, participant?.id ?? '')
};
}

View File

@@ -1,7 +1,6 @@
// @flow
import { connect } from 'react-redux';
import { IReduxState } from '../../../app/types';
import { translate } from '../../../base/i18n/functions';
import { isLocalParticipantModerator } from '../../../base/participants/functions';
import AbstractMuteButton, { _mapStateToProps as _abstractMapStateToProps } from '../AbstractMuteButton';
@@ -13,7 +12,7 @@ import AbstractMuteButton, { _mapStateToProps as _abstractMapStateToProps } from
* @param {Object} ownProps - Properties of component.
* @returns {Props}
*/
function _mapStateToProps(state, ownProps) {
function _mapStateToProps(state: IReduxState, ownProps: any) {
return {
..._abstractMapStateToProps(state, ownProps),
visible: isLocalParticipantModerator(state)

View File

@@ -1,4 +1,5 @@
import React from 'react';
import { ViewStyle } from 'react-native';
import Dialog from 'react-native-dialog';
import { Divider } from 'react-native-paper';
import { connect } from 'react-redux';
@@ -46,7 +47,8 @@ class MuteEveryoneDialog extends AbstractMuteEveryoneDialog<IProps> {
descriptionKey = { this.state.content }
onSubmit = { this._onSubmit }
title = { this.props.title } >
<Divider style = { styles.dividerDialog } />
{/* @ts-ignore */}
<Divider style = { styles.dividerDialog as ViewStyle } />
{ this._renderSwitch() }
</ConfirmDialog>
);

View File

@@ -1,7 +1,6 @@
// @flow
import { connect } from 'react-redux';
import { IReduxState } from '../../../app/types';
import { translate } from '../../../base/i18n/functions';
import { isLocalParticipantModerator } from '../../../base/participants/functions';
import AbstractMuteEveryoneElseButton from '../AbstractMuteEveryoneElseButton';
@@ -12,7 +11,7 @@ import AbstractMuteEveryoneElseButton from '../AbstractMuteEveryoneElseButton';
* @param {Object} state - The Redux state.
* @returns {Props}
*/
function _mapStateToProps(state) {
function _mapStateToProps(state: IReduxState) {
return {
visible: isLocalParticipantModerator(state)
};

View File

@@ -1,5 +1,3 @@
// @flow
import React from 'react';
import { connect } from 'react-redux';
@@ -29,8 +27,6 @@ class MuteRemoteParticipantsVideoDialog extends AbstractMuteRemoteParticipantsVi
onSubmit = { this._onSubmit } />
);
}
_onSubmit: () => boolean;
}
export default translate(connect(abstractMapStateToProps)(MuteRemoteParticipantsVideoDialog));

View File

@@ -1,7 +1,6 @@
// @flow
import { connect } from 'react-redux';
import { IReduxState } from '../../../app/types';
import { translate } from '../../../base/i18n/functions';
import { isLocalParticipantModerator } from '../../../base/participants/functions';
import AbstractMuteVideoButton, { _mapStateToProps as _abstractMapStateToProps } from '../AbstractMuteVideoButton';
@@ -13,7 +12,7 @@ import AbstractMuteVideoButton, { _mapStateToProps as _abstractMapStateToProps }
* @param {Object} ownProps - Properties of component.
* @returns {Props}
*/
function _mapStateToProps(state, ownProps) {
function _mapStateToProps(state: IReduxState, ownProps: any) {
return {
..._abstractMapStateToProps(state, ownProps),
visible: isLocalParticipantModerator(state)

View File

@@ -1,40 +1,29 @@
// @flow
import { connect } from 'react-redux';
import { IReduxState } from '../../../app/types';
import { translate } from '../../../base/i18n/functions';
import { IconEnlarge } from '../../../base/icons/svg';
import { pinParticipant } from '../../../base/participants/actions';
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
import { shouldDisplayTileView } from '../../../video-layout/functions';
export type Props = AbstractButtonProps & {
export interface IProps extends AbstractButtonProps {
/**
* True if tile view is currently enabled.
*/
_tileViewEnabled: boolean,
/**
* The redux {@code dispatch} function.
*/
dispatch: Function,
_tileViewEnabled?: boolean;
/**
* The ID of the participant that this button is supposed to pin.
*/
participantID: string,
/**
* The function to be used to translate i18n labels.
*/
t: Function
};
participantID: string;
}
/**
* A remote video menu button which pins a participant and exist the tile view.
*/
class PinButton extends AbstractButton<Props, *> {
class PinButton extends AbstractButton<IProps> {
accessibilityLabel = 'toolbar.accessibilityLabel.show';
icon = IconEnlarge;
label = 'videothumbnail.show';
@@ -57,9 +46,9 @@ class PinButton extends AbstractButton<Props, *> {
* Maps part of the Redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @returns {Props}
* @returns {IProps}
*/
function _mapStateToProps(state) {
function _mapStateToProps(state: IReduxState) {
const { isOpen } = state['features/participants-pane'];
return {

View File

@@ -1,8 +1,9 @@
import React, { PureComponent } from 'react';
import { Text, View } from 'react-native';
import { Text, TextStyle, View, ViewStyle } from 'react-native';
import { Divider } from 'react-native-paper';
import { connect } from 'react-redux';
import { IReduxState } from '../../../app/types';
import Avatar from '../../../base/avatar/components/Avatar';
import { hideSheet } from '../../../base/dialog/actions';
import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
@@ -16,10 +17,11 @@ import {
isLocalParticipantModerator
} from '../../../base/participants/functions';
import { getBreakoutRooms, getCurrentRoomId } from '../../../breakout-rooms/functions';
import { IRoom } from '../../../breakout-rooms/types';
import PrivateMessageButton from '../../../chat/components/native/PrivateMessageButton';
import ConnectionStatusButton from '../native/ConnectionStatusButton';
import AskUnmuteButton from './AskUnmuteButton';
import ConnectionStatusButton from './ConnectionStatusButton';
import GrantModeratorButton from './GrantModeratorButton';
import KickButton from './KickButton';
import MuteButton from './MuteButton';
@@ -31,85 +33,84 @@ import styles from './styles';
// import VolumeSlider from './VolumeSlider';
/**
* Size of the rendered avatar in the menu.
*/
const AVATAR_SIZE = 24;
type Props = {
/**
* The Redux dispatch function.
*/
dispatch: Function,
/**
* The ID of the participant for which this menu opened for.
*/
participantId: String,
interface IProps {
/**
* The id of the current room.
*/
_currentRoomId: String,
/**
* Whether or not to display the kick button.
*/
_disableKick: boolean,
/**
* Whether or not to display the send private message button.
*/
_disablePrivateChat: Boolean,
/**
* Whether or not to display the remote mute buttons.
*/
_disableRemoteMute: boolean,
_currentRoomId: string;
/**
* Whether or not to display the grant moderator button.
*/
_disableGrantModerator: Boolean,
_disableGrantModerator: boolean;
/**
* Whether or not to display the kick button.
*/
_disableKick: boolean;
/**
* Whether or not to display the send private message button.
*/
_disablePrivateChat: boolean;
/**
* Whether or not to display the remote mute buttons.
*/
_disableRemoteMute: boolean;
/**
* Whether the participant is present in the room or not.
*/
_isParticipantAvailable?: boolean,
_isParticipantAvailable?: boolean;
/**
* Whether the local participant is moderator or not.
*/
_moderator: boolean,
_moderator: boolean;
/**
* Display name of the participant retrieved from Redux.
*/
_participantDisplayName: string,
_participantDisplayName: string;
/**
* Array containing the breakout rooms.
*/
_rooms: Array<Object>,
_rooms: Array<IRoom>;
/**
* The Redux dispatch function.
*/
dispatch: Function;
/**
* The ID of the participant for which this menu opened for.
*/
participantId: string;
/**
* Translation function.
*/
t: Function
t: Function;
}
/**
* Class to implement a popup menu that opens upon long pressing a thumbnail.
*/
class RemoteVideoMenu extends PureComponent<Props> {
class RemoteVideoMenu extends PureComponent<IProps> {
/**
* Constructor of the component.
*
* @inheritdoc
*/
constructor(props: Props) {
constructor(props: IProps) {
super(props);
this._onCancel = this._onCancel.bind(this);
@@ -149,7 +150,8 @@ class RemoteVideoMenu extends PureComponent<Props> {
{ !_disableRemoteMute && <MuteButton { ...buttonProps } /> }
<MuteEveryoneElseButton { ...buttonProps } />
{ !_disableRemoteMute && <MuteVideoButton { ...buttonProps } /> }
<Divider style = { styles.divider } />
{/* @ts-ignore */}
<Divider style = { styles.divider as ViewStyle } />
{ !_disableKick && <KickButton { ...buttonProps } /> }
{ !_disableGrantModerator && <GrantModeratorButton { ...buttonProps } /> }
<PinButton { ...buttonProps } />
@@ -158,9 +160,10 @@ class RemoteVideoMenu extends PureComponent<Props> {
{ ...buttonProps }
afterClick = { undefined } />
{_moderator && _rooms.length > 1 && <>
<Divider style = { styles.divider } />
<View style = { styles.contextMenuItem }>
<Text style = { styles.contextMenuItemText }>
{/* @ts-ignore */}
<Divider style = { styles.divider as ViewStyle } />
<View style = { styles.contextMenuItem as ViewStyle }>
<Text style = { styles.contextMenuItemText as TextStyle }>
{t('breakoutRooms.actions.sendToBreakoutRoom')}
</Text>
</View>
@@ -196,11 +199,11 @@ class RemoteVideoMenu extends PureComponent<Props> {
<View
style = { [
bottomSheetStyles.sheet,
styles.participantNameContainer ] }>
styles.participantNameContainer ] as ViewStyle[] }>
<Avatar
participantId = { participantId }
size = { AVATAR_SIZE } />
<Text style = { styles.participantNameLabel }>
<Text style = { styles.participantNameLabel as TextStyle }>
{ this.props._participantDisplayName }
</Text>
</View>
@@ -214,9 +217,9 @@ class RemoteVideoMenu extends PureComponent<Props> {
* @param {Object} state - Redux state.
* @param {Object} ownProps - Properties of component.
* @private
* @returns {Props}
* @returns {IProps}
*/
function _mapStateToProps(state, ownProps) {
function _mapStateToProps(state: IReduxState, ownProps: any) {
const kickOutEnabled = getFeatureFlag(state, KICK_OUT_ENABLED, true);
const { participantId } = ownProps;
const { remoteVideoMenu = {}, disableRemoteMute } = state['features/base/config'];

View File

@@ -1,42 +1,32 @@
// @flow
import { connect } from 'react-redux';
import { createBreakoutRoomsEvent } from '../../../analytics/AnalyticsEvents';
import { sendAnalytics } from '../../../analytics/functions';
import { IReduxState } from '../../../app/types';
import { translate } from '../../../base/i18n/functions';
import { IconRingGroup } from '../../../base/icons/svg';
import { isLocalParticipantModerator } from '../../../base/participants/functions';
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
import { sendParticipantToRoom } from '../../../breakout-rooms/actions';
import { IRoom } from '../../../breakout-rooms/types';
export type Props = AbstractButtonProps & {
/**
* The redux {@code dispatch} function.
*/
dispatch: Function,
export interface IProps extends AbstractButtonProps {
/**
* ID of the participant to send to breakout room.
*/
participantID: string,
participantID: string;
/**
* Room to send participant to.
*/
room: Object,
/**
* Translation function.
*/
t: Function
};
room: IRoom;
}
/**
* An abstract remote video menu button which sends the remote participant to a breakout room.
*/
class SendToBreakoutRoom extends AbstractButton<Props, *> {
class SendToBreakoutRoom extends AbstractButton<IProps> {
accessibilityLabel = 'breakoutRooms.actions.sendToBreakoutRoom';
icon = IconRingGroup;
@@ -70,9 +60,9 @@ class SendToBreakoutRoom extends AbstractButton<Props, *> {
*
* @param {Object} state - The Redux state.
* @param {Object} ownProps - Properties of component.
* @returns {Props}
* @returns {IProps}
*/
function mapStateToProps(state) {
function mapStateToProps(state: IReduxState) {
return {
visible: isLocalParticipantModerator(state)
};

View File

@@ -1,10 +1,9 @@
// @flow
import React, { PureComponent } from 'react';
import { Text, View } from 'react-native';
import { Text, TextStyle, View, ViewStyle } from 'react-native';
import { Divider } from 'react-native-paper';
import { connect } from 'react-redux';
import { IReduxState, IStore } from '../../../app/types';
import Avatar from '../../../base/avatar/components/Avatar';
import { hideSheet } from '../../../base/dialog/actions';
import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
@@ -17,50 +16,49 @@ import SharedVideoButton from '../../../shared-video/components/native/SharedVid
import styles from './styles';
/**
* Size of the rendered avatar in the menu.
*/
const AVATAR_SIZE = 24;
type Props = {
/**
* The Redux dispatch function.
*/
dispatch: Function,
/**
* The ID of the participant for which this menu opened for.
*/
participantId: string,
interface IProps {
/**
* True if the menu is currently open, false otherwise.
*/
_isOpen: boolean,
_isOpen: boolean;
/**
* Whether the participant is present in the room or not.
*/
_isParticipantAvailable?: boolean,
_isParticipantAvailable?: boolean;
/**
* Display name of the participant retrieved from Redux.
*/
_participantDisplayName: string,
_participantDisplayName: string;
/**
* The Redux dispatch function.
*/
dispatch: IStore['dispatch'];
/**
* The ID of the participant for which this menu opened for.
*/
participantId: string;
}
/**
* Class to implement a popup menu that opens upon long pressing a fake participant thumbnail.
*/
class SharedVideoMenu extends PureComponent<Props> {
class SharedVideoMenu extends PureComponent<IProps> {
/**
* Constructor of the component.
*
* @inheritdoc
*/
constructor(props: Props) {
constructor(props: IProps) {
super(props);
this._onCancel = this._onCancel.bind(this);
@@ -89,7 +87,8 @@ class SharedVideoMenu extends PureComponent<Props> {
<BottomSheet
renderHeader = { this._renderMenuHeader }
showSlidingView = { _isParticipantAvailable }>
<Divider style = { styles.divider } />
{/* @ts-ignore */}
<Divider style = { styles.divider as ViewStyle } />
<SharedVideoButton { ...buttonProps } />
</BottomSheet>
);
@@ -117,11 +116,11 @@ class SharedVideoMenu extends PureComponent<Props> {
<View
style = { [
bottomSheetStyles.sheet,
styles.participantNameContainer ] }>
styles.participantNameContainer ] as ViewStyle[] }>
<Avatar
participantId = { participantId }
size = { AVATAR_SIZE } />
<Text style = { styles.participantNameLabel }>
<Text style = { styles.participantNameLabel as TextStyle }>
{ this.props._participantDisplayName }
</Text>
</View>
@@ -135,9 +134,9 @@ class SharedVideoMenu extends PureComponent<Props> {
* @param {Object} state - Redux state.
* @param {Object} ownProps - Properties of component.
* @private
* @returns {Props}
* @returns {IProps}
*/
function _mapStateToProps(state, ownProps) {
function _mapStateToProps(state: IReduxState, ownProps: any) {
const { participantId } = ownProps;
const isParticipantAvailable = getParticipantById(state, participantId);

View File

@@ -1,12 +1,11 @@
// @flow
import Slider from '@react-native-community/slider';
import _ from 'lodash';
import React, { PureComponent } from 'react';
import { View } from 'react-native';
import { View, ViewStyle } from 'react-native';
import { withTheme } from 'react-native-paper';
import { connect } from 'react-redux';
import { IReduxState } from '../../../app/types';
import Icon from '../../../base/icons/components/Icon';
import { IconVolumeUp } from '../../../base/icons/svg';
import { setVolume } from '../../../participants-pane/actions.native';
@@ -14,57 +13,55 @@ import { VOLUME_SLIDER_SCALE } from '../../constants';
import styles from './styles';
/**
* The type of the React {@code Component} props of {@link VolumeSlider}.
*/
type Props = {
interface IProps {
/**
* Whether the participant enters the conference silent.
*/
_startSilent: boolean,
_startSilent: boolean;
/**
* The volume level for the participant.
*/
_volume: number,
_volume: number;
/**
* The redux dispatch function.
*/
dispatch: Function,
dispatch: Function;
/**
* The ID of the participant.
*/
participantID: string,
participantID: string;
/**
* Theme used for styles.
*/
theme: Object
};
theme: any;
}
/**
* The type of the React {@code Component} state of {@link VolumeSlider}.
*/
type State = {
interface IState {
/**
* The volume of the participant's audio element. The value will
* be represented by a slider.
*/
volumeLevel: number
};
volumeLevel: number;
}
/**
* Component that renders the volume slider.
*
* @returns {React$Element<any>}
*/
class VolumeSlider extends PureComponent<Props, State> {
_onVolumeChange: Function;
class VolumeSlider extends PureComponent<IProps, IState> {
_originalVolumeChange: Function;
/**
@@ -73,7 +70,7 @@ class VolumeSlider extends PureComponent<Props, State> {
* @param {Object} props - The read-only properties with which the new
* instance is to be initialized.
*/
constructor(props: Props) {
constructor(props: IProps) {
super(props);
this.state = {
@@ -100,7 +97,7 @@ class VolumeSlider extends PureComponent<Props, State> {
const onVolumeChange = _startSilent ? undefined : this._onVolumeChange;
return (
<View style = { styles.volumeSliderContainer } >
<View style = { styles.volumeSliderContainer as ViewStyle } >
<Icon
size = { 24 }
src = { IconVolumeUp } />
@@ -110,7 +107,7 @@ class VolumeSlider extends PureComponent<Props, State> {
minimumTrackTintColor = { palette.action01 }
minimumValue = { 0 }
onValueChange = { onVolumeChange }
style = { styles.sliderContainer }
style = { styles.sliderContainer as ViewStyle }
thumbTintColor = { palette.ui10 }
value = { volumeLevel } />
</View>
@@ -126,7 +123,7 @@ class VolumeSlider extends PureComponent<Props, State> {
* @private
* @returns {void}
*/
_onVolumeChange(volumeLevel) {
_onVolumeChange(volumeLevel: number) {
const { dispatch, participantID } = this.props;
dispatch(setVolume(participantID, volumeLevel));
@@ -139,9 +136,9 @@ class VolumeSlider extends PureComponent<Props, State> {
*
* @param {Object} state - The Redux state.
* @param {Object} ownProps - The own props of the component.
* @returns {Props}
* @returns {IProps}
*/
function mapStateToProps(state, ownProps): Object {
function mapStateToProps(state: IReduxState, ownProps: any) {
const { participantID } = ownProps;
const { participantsVolume } = state['features/participants-pane'];
const { startSilent } = state['features/base/config'];

View File

@@ -1,13 +1,11 @@
// @flow
import { WithTranslation } from 'react-i18next';
import { translate } from '../../base/i18n/functions';
import ExpandedLabel, { Props as AbstractProps } from '../../base/label/components/native/ExpandedLabel';
import ExpandedLabel, { IProps as AbstractProps } from '../../base/label/components/native/ExpandedLabel';
import { AUD_LABEL_COLOR } from './styles';
type Props = AbstractProps & {
t: Function
}
type Props = AbstractProps & WithTranslation;
/**
* A react {@code Component} that implements an expanded label as tooltip-like

View File

@@ -1,5 +1,3 @@
// @flow
import React from 'react';
import { connect } from 'react-redux';
@@ -13,13 +11,13 @@ import AbstractVideoQualityLabel, {
} from './AbstractVideoQualityLabel';
import styles from './styles';
type Props = AbstractProps & {
interface IProps extends AbstractProps {
/**
* Style of the component passed as props.
*/
style: ?StyleType
};
style?: StyleType;
}
/**
* React {@code Component} responsible for displaying a label that indicates
@@ -30,7 +28,7 @@ type Props = 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<Props> {
class VideoQualityLabel extends AbstractVideoQualityLabel<IProps> {
/**
* Implements React {@link Component}'s render.
@@ -46,7 +44,7 @@ class VideoQualityLabel extends AbstractVideoQualityLabel<Props> {
}
return (
<Label
<Label // @ts-ignore
style = { combineStyles(styles.indicatorAudioOnly, style) }
text = { t('videoStatus.audioOnly') } />
);

View File

@@ -55,6 +55,7 @@ external_services = {
};
muc_mapper_domain_base = 'vX.meet.jitsi';
main_domain = 'jitmeet.example.com';
-- https://prosody.im/doc/modules/mod_smacks
smacks_max_unacked_stanzas = 5;

View File

@@ -20,18 +20,28 @@ local get_room_from_jid = util.get_room_from_jid;
local get_focus_occupant = util.get_focus_occupant;
local internal_room_jid_match_rewrite = util.internal_room_jid_match_rewrite;
-- this is the main virtual host of this vnode
local local_domain = module:get_option_string('muc_mapper_domain_base');
if not local_domain then
module:log('warn', "No 'muc_mapper_domain_base' option set, disabling fmuc plugin");
return;
end
-- this is the main virtual host of the main prosody that this vnode serves
local main_domain = module:get_option_string('main_domain');
if not main_domain then
module:log('warn', "No 'main_domain' option set, disabling fmuc plugin");
return;
end
local muc_domain_prefix = module:get_option_string('muc_mapper_domain_prefix', 'conference');
local main_domain = string.gsub(module.host, muc_domain_prefix..'.', '');
local NICK_NS = 'http://jabber.org/protocol/nick';
-- we send stats for the total number of rooms, total number of participants and total number of visitors
local measure_rooms = module:measure("vnode-rooms", "amount");
local measure_participants = module:measure("vnode-participants", "amount");
local measure_visitors = module:measure("vnode-visitors", "amount");
-- This is the domain of the main prosody that is federating with us;
local fmuc_main_domain;
local measure_rooms = module:measure('vnode-rooms', 'amount');
local measure_participants = module:measure('vnode-participants', 'amount');
local measure_visitors = module:measure('vnode-visitors', 'amount');
local sent_iq_cache = require 'util.cache'.new(200);
@@ -45,12 +55,8 @@ module:hook('muc-occupant-pre-join', function (event)
local occupant, session = event.occupant, event.origin;
local node, host = jid.split(occupant.bare_jid);
if host == main_domain then
if host == local_domain then
occupant.role = 'visitor';
elseif not fmuc_main_domain then
if node ~= 'focus' then
fmuc_main_domain = host;
end
end
end, 3);
@@ -103,7 +109,7 @@ module:hook('muc-occupant-left', function (event)
local room, occupant = event.room, event.occupant;
local occupant_domain = jid.host(occupant.bare_jid);
if occupant_domain == main_domain then
if occupant_domain == local_domain then
local focus_occupant = get_focus_occupant(room);
if not focus_occupant then
module:log('warn', 'No focus found for %s', room.jid);
@@ -181,11 +187,11 @@ module:hook('muc-broadcast-presence', function (event)
sent_iq_cache:set(iq_id, socket.gettime());
local promotion_request = st.iq({
type = 'set',
to = 'visitors.'..fmuc_main_domain,
from = main_domain,
to = 'visitors.'..main_domain,
from = local_domain,
id = iq_id })
:tag('visitors', { xmlns = 'jitsi:visitors',
room = jid.join(jid.node(room.jid), muc_domain_prefix..'.'..fmuc_main_domain) })
room = jid.join(jid.node(room.jid), muc_domain_prefix..'.'..main_domain) })
:tag('promotion-request', { xmlns = 'jitsi:visitors', jid = occupant.jid }):up();
local nick_element = occupant:get_presence():get_child('nick', NICK_NS);
@@ -221,8 +227,8 @@ local function stanza_handler(event)
return;
end
if stanza.attr.from ~= 'visitors.'..fmuc_main_domain then
module:log('warn', 'not from visitors component, ignore! %s %s', stanza.attr.from, stanza);
if stanza.attr.from ~= 'visitors.'..main_domain then
module:log('warn', 'not from visitors component, ignore! %s', stanza);
return true;
end
@@ -241,7 +247,7 @@ local function stanza_handler(event)
-- respond with successful receiving the iq
origin.send(st.iq({
type = "result";
type = 'result';
from = stanza.attr.to;
to = stanza.attr.from;
id = stanza.attr.id
@@ -276,12 +282,12 @@ function process_host_module(name, callback)
process_host(name);
end
end
process_host_module(main_domain, function(host_module, host)
process_host_module(local_domain, function(host_module, host)
host_module:hook('iq/host', stanza_handler, 10);
end);
-- only live chat is supported for visitors
module:hook("muc-occupant-groupchat", function(event)
module:hook('muc-occupant-groupchat', function(event)
local occupant, room, stanza = event.occupant, event.room, event.stanza;
local from = stanza.attr.from;
local occupant_host = jid.host(occupant.bare_jid);
@@ -289,7 +295,7 @@ module:hook("muc-occupant-groupchat", function(event)
-- if there is no occupant this is a message from main, probably coming from other vnode
if occupant then
-- we manage nick only for visitors
if occupant_host ~= fmuc_main_domain then
if occupant_host ~= main_domain then
-- add to message stanza display name for the visitor
-- remove existing nick to avoid forgery
stanza:remove_children('nick', NICK_NS);
@@ -310,15 +316,15 @@ module:hook("muc-occupant-groupchat", function(event)
-- let's send it to main chat and rest of visitors here
for _, o in room:each_occupant() do
-- filter remote occupants
if jid.host(o.bare_jid) == main_domain then
if jid.host(o.bare_jid) == local_domain then
room:route_to_occupant(o, stanza)
end
end
-- send to main participants only messages from local occupants (skip from remote vnodes)
if occupant and occupant_host ~= fmuc_main_domain then
if occupant and occupant_host ~= main_domain then
local main_message = st.clone(stanza);
main_message.attr.to = jid.join(jid.node(room.jid), muc_domain_prefix..'.'..fmuc_main_domain);
main_message.attr.to = jid.join(jid.node(room.jid), muc_domain_prefix..'.'..main_domain);
module:send(main_message);
end
stanza.attr.from = from; -- something prosody does internally
@@ -328,20 +334,20 @@ end, 55); -- prosody check for visitor's chat is prio 50, we want to override it
module:hook('muc-private-message', function(event)
-- private messaging is forbidden
event.origin.send(st.error_reply(event.stanza, "auth", "forbidden",
"Private messaging is disabled on visitor nodes"));
event.origin.send(st.error_reply(event.stanza, 'auth', 'forbidden',
'Private messaging is disabled on visitor nodes'));
return true;
end, 10);
-- we calculate the stats on the configured interval (60 seconds by default)
module:hook_global("stats-update", function ()
module:hook_global('stats-update', function ()
local participants_count, rooms_count, visitors_count = 0, 0, 0;
-- iterate over all rooms
for room in prosody.hosts[module.host].modules.muc.each_room() do
rooms_count = rooms_count + 1;
for _, o in room:each_occupant() do
if jid.host(o.bare_jid) == main_domain then
if jid.host(o.bare_jid) == local_domain then
visitors_count = visitors_count + 1;
else
participants_count = participants_count + 1;
@@ -356,4 +362,80 @@ module:hook_global("stats-update", function ()
measure_participants(participants_count);
end);
-- we skip it till the main participants are added from the main prosody
module:hook('jicofo-unlock-room', function(e)
-- we do not block events we fired
if e.fmuc_fired then
return;
end
return true;
end);
-- handles incoming iq connect stanzas
local function iq_from_main_handler(event)
local origin, stanza = event.origin, event.stanza;
if stanza.name ~= 'iq' then
return;
end
if stanza.attr.type == 'result' and sent_iq_cache:get(stanza.attr.id) then
sent_iq_cache:set(stanza.attr.id, nil);
return true;
end
if stanza.attr.type ~= 'set' then
return;
end
local visitors_iq = event.stanza:get_child('visitors', 'jitsi:visitors');
if not visitors_iq then
return;
end
if stanza.attr.from ~= main_domain then
module:log('warn', 'not from main prosody, ignore! %s', stanza);
return true;
end
local room_jid = visitors_iq.attr.room;
local room = get_room_from_jid(room_jid_match_rewrite(room_jid));
if not room then
module:log('warn', 'No room found %s', room_jid);
return;
end
local node = visitors_iq:get_child('connect');
local fire_jicofo_unlock = true;
if not node then
node = visitors_iq:get_child('update');
fire_jicofo_unlock = false;
end
if not node then
return;
end
-- respond with successful receiving the iq
origin.send(st.iq({
type = 'result';
from = stanza.attr.to;
to = stanza.attr.from;
id = stanza.attr.id
}));
-- if there is password supplied use it
-- if this is update it will either set or remove the password
room:set_password(node.attr.password);
if fire_jicofo_unlock then
-- everything is connected allow participants to join
module:fire_event('jicofo-unlock-room', { room = room; fmuc_fired = true; });
end
return true;
end
module:hook('iq/host', iq_from_main_handler, 10);

View File

@@ -10,6 +10,7 @@
--- NOTE: Make sure all communication between prosodies is using the real jids ([foo]room1@muc.example.com)
local st = require 'util.stanza';
local jid = require 'util.jid';
local new_id = require 'util.id'.medium;
local util = module:require 'util';
local presence_check_status = util.presence_check_status;
@@ -36,6 +37,8 @@ local ignore_list = module:get_option_set('visitors_ignore_list', {});
-- Advertise the component for discovery via disco#items
module:add_identity('component', 'visitors', 'visitors.'..module.host);
local sent_iq_cache = require 'util.cache'.new(200);
-- visitors_nodes = {
-- roomjid1 = {
-- nodes = {
@@ -47,6 +50,24 @@ module:add_identity('component', 'visitors', 'visitors.'..module.host);
--}
local visitors_nodes = {};
-- sends connect or update iq
-- @parameter type - Type of iq to send 'connect' or 'update'
local function send_visitors_iq(conference_service, room, type)
-- send iq informing the vnode that the connect is done and it will allow visitors to join
local iq_id = new_id();
sent_iq_cache:set(iq_id, socket.gettime());
local connect_done = st.iq({
type = 'set',
to = conference_service,
from = module.host,
id = iq_id })
:tag('visitors', { xmlns = 'jitsi:visitors',
room = jid.join(jid.node(room.jid), conference_service) })
:tag(type, { xmlns = 'jitsi:visitors', password = room:get_password() or '' }):up();
module:send(connect_done);
end
-- an event received from visitors component, which receives iqs from jicofo
local function connect_vnode(event)
local room, vnode = event.room, event.vnode;
@@ -75,7 +96,15 @@ local function connect_vnode(event)
fmuc_pr.attr.to = jid.join(user, conference_service , res);
fmuc_pr.attr.from = o.jid;
-- add <x>
fmuc_pr:tag('x', { xmlns = MUC_NS }):up();
fmuc_pr:tag('x', { xmlns = MUC_NS });
-- if there is a password on the main room let's add the password for the vnode join
-- as we will set the password to the vnode room and we will need it
local pass = room:get_password();
if pass and pass ~= '' then
fmuc_pr:tag('password'):text(pass);
end
fmuc_pr:up();
module:send(fmuc_pr);
@@ -83,9 +112,26 @@ local function connect_vnode(event)
end
end
visitors_nodes[room.jid].nodes[conference_service] = sent_main_participants;
send_visitors_iq(conference_service, room, 'connect');
end
module:hook('jitsi-connect-vnode', connect_vnode);
-- listens for responses to the iq sent for connecting vnode
local function stanza_handler(event)
local origin, stanza = event.origin, event.stanza;
if stanza.name ~= 'iq' then
return;
end
if stanza.attr.type == 'result' and sent_iq_cache:get(stanza.attr.id) then
sent_iq_cache:set(stanza.attr.id, nil);
return true;
end
end
module:hook('iq/host', stanza_handler, 10);
-- an event received from visitors component, which receives iqs from jicofo
local function disconnect_vnode(event)
local room, vnode = event.room, event.vnode;
@@ -211,7 +257,7 @@ process_host_module(main_muc_component_config, function(host_module, host)
end
end);
-- forwards messages from main participants to vnodes
host_module:hook("muc-occupant-groupchat", function(event)
host_module:hook('muc-occupant-groupchat', function(event)
local room, stanza, occupant = event.room, event.stanza, event.occupant;
-- filter sending messages from transcribers/jibris to visitors
@@ -231,7 +277,7 @@ process_host_module(main_muc_component_config, function(host_module, host)
end);
-- receiving messages from visitor nodes and forward them to local main participants
-- and forward them to the rest of visitor nodes
host_module:hook("muc-occupant-groupchat", function(event)
host_module:hook('muc-occupant-groupchat', function(event)
local occupant, room, stanza = event.occupant, event.room, event.stanza;
local to = stanza.attr.to;
local from = stanza.attr.from;
@@ -260,4 +306,18 @@ process_host_module(main_muc_component_config, function(host_module, host)
return true;
end, 55); -- prosody check for unknown participant chat is prio 50, we want to override it
host_module:hook('muc-config-submitted/muc#roomconfig_roomsecret', function(event)
if event.status_codes['104'] then
local room = event.room;
if visitors_nodes[room.jid] then
-- we need to update all vnodes
local vnodes = visitors_nodes[room.jid].nodes;
for conference_service in pairs(vnodes) do
send_visitors_iq(conference_service, room, 'update');
end
end
end
end, -100); -- we want to run last in order to check is the status code 104
end);

View File

@@ -8,7 +8,9 @@ local get_room_from_jid = util.get_room_from_jid;
local get_focus_occupant = util.get_focus_occupant;
local get_room_by_name_and_subdomain = util.get_room_by_name_and_subdomain;
local new_id = require 'util.id'.medium;
local um_is_admin = require "core.usermanager".is_admin;
local um_is_admin = require 'core.usermanager'.is_admin;
local MUC_NS = 'http://jabber.org/protocol/muc';
local muc_domain_prefix = module:get_option_string('muc_mapper_domain_prefix', 'conference');
local muc_domain_base = module:get_option_string('muc_mapper_domain_base');
@@ -17,7 +19,7 @@ if not muc_domain_base then
return;
end
local auto_allow_promotion = module:get_option_boolean("auto_allow_visitor_promotion", false);
local auto_allow_promotion = module:get_option_boolean('auto_allow_visitor_promotion', false);
local function is_admin(jid)
return um_is_admin(jid, module.host);
@@ -33,7 +35,7 @@ local sent_iq_cache = require 'util.cache'.new(200);
local function respond_iq_result(origin, stanza)
-- respond with successful receiving the iq
origin.send(st.iq({
type = "result";
type = 'result';
from = stanza.attr.to;
to = stanza.attr.from;
id = stanza.attr.id
@@ -158,7 +160,7 @@ local function stanza_handler(event)
return processed;
end
module:hook("iq/host", stanza_handler, 10);
module:hook('iq/host', stanza_handler, 10);
--process a host module directly if loaded or hooks to wait for its load
function process_host_module(name, callback)
@@ -184,6 +186,14 @@ process_host_module(muc_domain_prefix..'.'..muc_domain_base, function(host_modul
host_module:hook('muc-occupant-pre-join', function (event)
local room, stanza, origin = event.room, event.stanza, event.origin;
-- visitors were already in the room one way or another they have access
-- skip password challenge
local join = stanza:get_child('x', MUC_NS);
if join and room:get_password() and
visitors_promotion_map[room.jid] and visitors_promotion_map[room.jid][jid.node(stanza.attr.from)] then
join:tag('password', { xmlns = MUC_NS }):text(room:get_password());
end
-- we skip any checks when auto-allow is enabled
if auto_allow_promotion then
return;
@@ -191,7 +201,7 @@ process_host_module(muc_domain_prefix..'.'..muc_domain_base, function(host_modul
if visitors_promotion_map[room.jid] then
-- now let's check for jid
if visitors_promotion_map[room.jid] and visitors_promotion_map[room.jid][jid.node(stanza.attr.from)] then
if visitors_promotion_map[room.jid][jid.node(stanza.attr.from)] then
-- allow join
return;
end
@@ -208,7 +218,7 @@ end);
-- enable only in case of auto-allow is enabled
if auto_allow_promotion then
prosody.events.add_handler("pre-jitsi-authentication", function(session)
prosody.events.add_handler('pre-jitsi-authentication', function(session)
if not session.customusername or not session.jitsi_web_query_room then
return nil;
end