mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2025-12-30 03:12:29 +00:00
ref(TS) Convert some native components to TS (#13259)
This commit is contained in:
@@ -48,7 +48,7 @@ export interface IProps {
|
||||
* The style (as in stylesheet) to be applied to this
|
||||
* {@code AbstractContainer}.
|
||||
*/
|
||||
style?: StyleType;
|
||||
style?: StyleType | StyleType[];
|
||||
|
||||
tabIndex?: number;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ export * from './functions.any';
|
||||
* @param {StyleType} style - The passed style prop to the component.
|
||||
* @returns {StyleType}
|
||||
*/
|
||||
export function getFixedPlatformStyle(style?: StyleType): StyleType {
|
||||
export function getFixedPlatformStyle(style?: StyleType | StyleType[]) {
|
||||
// There is nothing to do on mobile - yet.
|
||||
|
||||
return style ?? {};
|
||||
|
||||
@@ -9,7 +9,7 @@ export * from './functions.any';
|
||||
* @param {StyleType} style - The passed style prop to the component.
|
||||
* @returns {StyleType}
|
||||
*/
|
||||
export function getFixedPlatformStyle(style?: StyleType) {
|
||||
export function getFixedPlatformStyle(style?: StyleType | StyleType[]) {
|
||||
if (Array.isArray(style)) {
|
||||
const _style = {};
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
|
||||
import React from 'react';
|
||||
import { StyleProp, View, ViewStyle } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
@@ -12,12 +10,10 @@ import {
|
||||
getParticipantById,
|
||||
isScreenShareParticipant
|
||||
} from '../../../base/participants/functions';
|
||||
// @ts-ignore
|
||||
import BaseIndicator from '../../../base/react/components/native/BaseIndicator';
|
||||
import {
|
||||
getTrackByMediaTypeAndParticipant
|
||||
} from '../../../base/tracks/functions.native';
|
||||
// @ts-ignore
|
||||
import indicatorStyles from '../../../filmstrip/components/native/styles';
|
||||
import {
|
||||
isTrackStreamingStatusInactive,
|
||||
@@ -50,12 +46,12 @@ type IProps = AbstractProps & {
|
||||
/**
|
||||
* Whether the connection is inactive or not.
|
||||
*/
|
||||
_isConnectionStatusInactive: boolean;
|
||||
_isConnectionStatusInactive?: boolean;
|
||||
|
||||
/**
|
||||
* Whether the connection is interrupted or not.
|
||||
*/
|
||||
_isConnectionStatusInterrupted: boolean;
|
||||
_isConnectionStatusInterrupted?: boolean;
|
||||
|
||||
/**
|
||||
* Whether the current participant is a virtual screenshare.
|
||||
@@ -70,7 +66,7 @@ type IProps = AbstractProps & {
|
||||
/**
|
||||
* Icon style override.
|
||||
*/
|
||||
iconStyle: any;
|
||||
iconStyle?: any;
|
||||
};
|
||||
|
||||
type IState = {
|
||||
@@ -127,12 +123,10 @@ class ConnectionIndicator extends AbstractConnectionIndicator<IProps, IState> {
|
||||
_isVirtualScreenshareParticipant,
|
||||
_isConnectionStatusInactive,
|
||||
_isConnectionStatusInterrupted
|
||||
// @ts-ignore
|
||||
} = this.props;
|
||||
const {
|
||||
showIndicator,
|
||||
stats
|
||||
// @ts-ignore
|
||||
} = this.state;
|
||||
const { percent } = stats;
|
||||
|
||||
@@ -167,10 +161,8 @@ class ConnectionIndicator extends AbstractConnectionIndicator<IProps, IState> {
|
||||
indicatorStyles.indicatorContainer as StyleProp<ViewStyle>,
|
||||
{ backgroundColor: indicatorColor }
|
||||
] }>
|
||||
{/* @ts-ignore */}
|
||||
<BaseIndicator
|
||||
icon = { IconConnection }
|
||||
// @ts-ignore
|
||||
iconStyle = { this.props.iconStyle || iconStyle } />
|
||||
</View>
|
||||
);
|
||||
@@ -185,7 +177,7 @@ class ConnectionIndicator extends AbstractConnectionIndicator<IProps, IState> {
|
||||
* @param {IProps} ownProps - The own props of the component.
|
||||
* @returns {IProps}
|
||||
*/
|
||||
export function _mapStateToProps(state: IReduxState, ownProps: IProps) {
|
||||
export function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
const { participantId } = ownProps;
|
||||
const tracks = state['features/base/tracks'];
|
||||
const participant = participantId ? getParticipantById(state, participantId) : getLocalParticipant(state);
|
||||
@@ -212,5 +204,4 @@ export function _mapStateToProps(state: IReduxState, ownProps: IProps) {
|
||||
};
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
export default connect(_mapStateToProps)(ConnectionIndicator);
|
||||
|
||||
@@ -20,7 +20,7 @@ export interface IProps {
|
||||
/**
|
||||
* Implements an abstract class for the RaisedHandIndicator component.
|
||||
*/
|
||||
export default class AbstractRaisedHandIndicator<P extends IProps>
|
||||
export default abstract class AbstractRaisedHandIndicator<P extends IProps>
|
||||
extends Component<P> {
|
||||
|
||||
/**
|
||||
@@ -41,7 +41,7 @@ export default class AbstractRaisedHandIndicator<P extends IProps>
|
||||
*
|
||||
* @returns {React$Element<*>}
|
||||
*/
|
||||
_renderIndicator: () => React.ReactElement;
|
||||
abstract _renderIndicator(): React.ReactElement;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { IconMicSlash } from '../../../base/icons/svg';
|
||||
@@ -1,10 +1,9 @@
|
||||
// @flow
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
import { FlatList } from 'react-native';
|
||||
import { FlatList, ViewStyle, ViewToken } from 'react-native';
|
||||
import { SafeAreaView, withSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState, IStore } from '../../../app/types';
|
||||
import { getLocalParticipant } from '../../../base/participants/functions';
|
||||
import Platform from '../../../base/react/Platform.native';
|
||||
import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui/constants';
|
||||
@@ -24,54 +23,54 @@ import styles from './styles';
|
||||
|
||||
|
||||
// Immutable reference to avoid re-renders.
|
||||
const NO_REMOTE_VIDEOS = [];
|
||||
const NO_REMOTE_VIDEOS: any[] = [];
|
||||
|
||||
/**
|
||||
* Filmstrip component's property types.
|
||||
*/
|
||||
type Props = {
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* Application's aspect ratio.
|
||||
*/
|
||||
_aspectRatio: Symbol,
|
||||
_aspectRatio: Symbol;
|
||||
|
||||
_clientWidth: number,
|
||||
_clientHeight: number;
|
||||
|
||||
_clientHeight: number,
|
||||
_clientWidth: number;
|
||||
|
||||
/**
|
||||
* Whether or not to hide the self view.
|
||||
*/
|
||||
_disableSelfView: boolean,
|
||||
_disableSelfView: boolean;
|
||||
|
||||
/**
|
||||
* Whether or not the toolbox is displayed.
|
||||
*/
|
||||
_toolboxVisible: Boolean,
|
||||
|
||||
_localParticipantId: string,
|
||||
_localParticipantId: string;
|
||||
|
||||
/**
|
||||
* The participants in the conference.
|
||||
*/
|
||||
_participants: Array<any>,
|
||||
_participants: Array<any>;
|
||||
|
||||
/**
|
||||
* Whether or not the toolbox is displayed.
|
||||
*/
|
||||
_toolboxVisible: Boolean;
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the filmstrip is visible.
|
||||
*/
|
||||
_visible: boolean,
|
||||
_visible: boolean;
|
||||
|
||||
/**
|
||||
* Invoked to trigger state changes in Redux.
|
||||
*/
|
||||
dispatch: Function,
|
||||
dispatch: IStore['dispatch'];
|
||||
|
||||
/**
|
||||
* Object containing the safe area insets.
|
||||
*/
|
||||
insets: Object,
|
||||
};
|
||||
insets: Object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a React {@link Component} which represents the filmstrip on
|
||||
@@ -79,7 +78,7 @@ type Props = {
|
||||
*
|
||||
* @augments Component
|
||||
*/
|
||||
class Filmstrip extends PureComponent<Props> {
|
||||
class Filmstrip extends PureComponent<IProps> {
|
||||
/**
|
||||
* Whether the local participant should be rendered separately from the
|
||||
* remote participants ie outside of their {@link ScrollView}.
|
||||
@@ -96,7 +95,7 @@ class Filmstrip extends PureComponent<Props> {
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props) {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
// XXX Our current design is to have the local participant separate from
|
||||
@@ -129,15 +128,13 @@ class Filmstrip extends PureComponent<Props> {
|
||||
this._renderThumbnail = this._renderThumbnail.bind(this);
|
||||
}
|
||||
|
||||
_keyExtractor: string => string;
|
||||
|
||||
/**
|
||||
* Returns a key for a passed item of the list.
|
||||
*
|
||||
* @param {string} item - The user ID.
|
||||
* @returns {string} - The user ID.
|
||||
*/
|
||||
_keyExtractor(item) {
|
||||
_keyExtractor(item: string) {
|
||||
return item;
|
||||
}
|
||||
|
||||
@@ -166,16 +163,14 @@ class Filmstrip extends PureComponent<Props> {
|
||||
});
|
||||
}
|
||||
|
||||
_getItemLayout: (?Array<string>, number) => {length: number, offset: number, index: number};
|
||||
|
||||
/**
|
||||
* Optimization for FlatList. Returns the length, offset and index for an item.
|
||||
*
|
||||
* @param {Array<string>} data - The data array with user IDs.
|
||||
* @param {Array<string>} _data - The data array with user IDs.
|
||||
* @param {number} index - The index number of the item.
|
||||
* @returns {Object}
|
||||
*/
|
||||
_getItemLayout(data, index) {
|
||||
_getItemLayout(_data: string[] | null | undefined, index: number) {
|
||||
const { _aspectRatio } = this.props;
|
||||
const isNarrowAspectRatio = _aspectRatio === ASPECT_RATIO_NARROW;
|
||||
const length = isNarrowAspectRatio ? styles.thumbnail.width : styles.thumbnail.height;
|
||||
@@ -187,8 +182,6 @@ class Filmstrip extends PureComponent<Props> {
|
||||
};
|
||||
}
|
||||
|
||||
_onViewableItemsChanged: Object => void;
|
||||
|
||||
/**
|
||||
* A handler for visible items changes.
|
||||
*
|
||||
@@ -196,7 +189,7 @@ class Filmstrip extends PureComponent<Props> {
|
||||
* @param {Array<Object>} data.viewableItems - The visible items array.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onViewableItemsChanged({ viewableItems = [] }) {
|
||||
_onViewableItemsChanged({ viewableItems = [] }: { viewableItems: ViewToken[]; }) {
|
||||
const { _disableSelfView } = this.props;
|
||||
|
||||
if (!this._separateLocalThumbnail && !_disableSelfView && viewableItems[0]?.index === 0) {
|
||||
@@ -209,8 +202,8 @@ class Filmstrip extends PureComponent<Props> {
|
||||
return;
|
||||
}
|
||||
|
||||
let startIndex = viewableItems[0].index;
|
||||
let endIndex = viewableItems[viewableItems.length - 1].index;
|
||||
let startIndex = Number(viewableItems[0].index);
|
||||
let endIndex = Number(viewableItems[viewableItems.length - 1].index);
|
||||
|
||||
if (!this._separateLocalThumbnail && !_disableSelfView) {
|
||||
// We are off by one in the remote participants array.
|
||||
@@ -221,15 +214,13 @@ class Filmstrip extends PureComponent<Props> {
|
||||
this.props.dispatch(setVisibleRemoteParticipants(startIndex, endIndex));
|
||||
}
|
||||
|
||||
_renderThumbnail: Object => Object;
|
||||
|
||||
/**
|
||||
* Creates React Element to display each participant in a thumbnail.
|
||||
*
|
||||
* @private
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderThumbnail({ item /* , index , separators */ }) {
|
||||
_renderThumbnail({ item }: { item: string; }) {
|
||||
return (
|
||||
<Thumbnail
|
||||
key = { item }
|
||||
@@ -277,9 +268,9 @@ class Filmstrip extends PureComponent<Props> {
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeAreaView
|
||||
<SafeAreaView // @ts-ignore
|
||||
edges = { [ bottomEdge && 'bottom', 'left', 'right' ].filter(Boolean) }
|
||||
style = { filmstripStyle }>
|
||||
style = { filmstripStyle as ViewStyle }>
|
||||
{
|
||||
this._separateLocalThumbnail
|
||||
&& !isNarrowAspectRatio
|
||||
@@ -317,9 +308,9 @@ class Filmstrip extends PureComponent<Props> {
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @private
|
||||
* @returns {Props}
|
||||
* @returns {IProps}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
const { enabled, remoteParticipants } = state['features/filmstrip'];
|
||||
const disableSelfView = getHideSelfView(state);
|
||||
const showRemoteVideos = shouldRemoteVideosBeVisible(state);
|
||||
@@ -330,7 +321,7 @@ function _mapStateToProps(state) {
|
||||
_clientHeight: responsiveUI.clientHeight,
|
||||
_clientWidth: responsiveUI.clientWidth,
|
||||
_disableSelfView: disableSelfView,
|
||||
_localParticipantId: getLocalParticipant(state)?.id,
|
||||
_localParticipantId: getLocalParticipant(state)?.id ?? '',
|
||||
_participants: showRemoteVideos ? remoteParticipants : NO_REMOTE_VIDEOS,
|
||||
_toolboxVisible: isToolboxVisible(state),
|
||||
_visible: enabled && isFilmstripVisible(state)
|
||||
@@ -1,7 +1,5 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { View, ViewStyle } from 'react-native';
|
||||
|
||||
import Thumbnail from './Thumbnail';
|
||||
import styles from './styles';
|
||||
@@ -14,7 +12,7 @@ import styles from './styles';
|
||||
*/
|
||||
export default function LocalThumbnail() {
|
||||
return (
|
||||
<View style = { styles.localThumbnail }>
|
||||
<View style = { styles.localThumbnail as ViewStyle }>
|
||||
<Thumbnail />
|
||||
</View>
|
||||
);
|
||||
@@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { IconPin } from '../../../base/icons/svg';
|
||||
@@ -1,12 +1,11 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { View, ViewStyle } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IconRaiseHand } from '../../../base/icons/svg';
|
||||
import BaseIndicator from '../../../base/react/components/native/BaseIndicator';
|
||||
import AbstractRaisedHandIndicator, {
|
||||
IProps,
|
||||
_mapStateToProps
|
||||
} from '../AbstractRaisedHandIndicator';
|
||||
|
||||
@@ -18,7 +17,7 @@ import styles from './styles';
|
||||
*
|
||||
* @augments Component
|
||||
*/
|
||||
class RaisedHandIndicator extends AbstractRaisedHandIndicator<Props> {
|
||||
class RaisedHandIndicator extends AbstractRaisedHandIndicator<IProps> {
|
||||
/**
|
||||
* Renders the platform specific indicator element.
|
||||
*
|
||||
@@ -26,7 +25,7 @@ class RaisedHandIndicator extends AbstractRaisedHandIndicator<Props> {
|
||||
*/
|
||||
_renderIndicator() {
|
||||
return (
|
||||
<View style = { styles.raisedHandIndicator }>
|
||||
<View style = { styles.raisedHandIndicator as ViewStyle }>
|
||||
<BaseIndicator
|
||||
icon = { IconRaiseHand }
|
||||
iconStyle = { styles.raisedHandIcon } />
|
||||
@@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { IconScreenshare } from '../../../base/icons/svg';
|
||||
@@ -1,8 +1,8 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { Image, View } from 'react-native';
|
||||
import { Image, ImageStyle, View, ViewStyle } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { IReduxState, IStore } from '../../../app/types';
|
||||
import { JitsiTrackEvents } from '../../../base/lib-jitsi-meet';
|
||||
import { MEDIA_TYPE, VIDEO_TYPE } from '../../../base/media/constants';
|
||||
import { pinParticipant } from '../../../base/participants/actions';
|
||||
@@ -18,14 +18,16 @@ import {
|
||||
} from '../../../base/participants/functions';
|
||||
import { FakeParticipant } from '../../../base/participants/types';
|
||||
import Container from '../../../base/react/components/native/Container';
|
||||
import { StyleType } from '../../../base/styles/functions.any';
|
||||
import { trackStreamingStatusChanged } from '../../../base/tracks/actions.native';
|
||||
import {
|
||||
getTrackByMediaTypeAndParticipant,
|
||||
getVideoTrackByParticipant
|
||||
} from '../../../base/tracks/functions.native';
|
||||
import { ITrack } from '../../../base/tracks/types';
|
||||
import ConnectionIndicator from '../../../connection-indicator/components/native/ConnectionIndicator';
|
||||
import DisplayNameLabel from '../../../display-name/components/native/DisplayNameLabel';
|
||||
import { getGifDisplayMode, getGifForParticipant } from '../../../gifs/functions';
|
||||
import { getGifDisplayMode, getGifForParticipant } from '../../../gifs/functions.native';
|
||||
import {
|
||||
showConnectionStatus,
|
||||
showContextMenuDetails,
|
||||
@@ -44,42 +46,42 @@ import styles, { AVATAR_SIZE } from './styles';
|
||||
/**
|
||||
* Thumbnail component's property types.
|
||||
*/
|
||||
type Props = {
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* Whether local audio (microphone) is muted or not.
|
||||
*/
|
||||
_audioMuted: boolean,
|
||||
|
||||
/**
|
||||
* URL of GIF sent by this participant, null if there's none.
|
||||
*/
|
||||
_gifSrc: ?string,
|
||||
_audioMuted: boolean;
|
||||
|
||||
/**
|
||||
* The type of participant if the participant is fake.
|
||||
*/
|
||||
_fakeParticipant?: FakeParticipant,
|
||||
_fakeParticipant?: FakeParticipant;
|
||||
|
||||
/**
|
||||
* URL of GIF sent by this participant, null if there's none.
|
||||
*/
|
||||
_gifSrc?: string;
|
||||
|
||||
/**
|
||||
* Indicates whether the participant is screen sharing.
|
||||
*/
|
||||
_isScreenShare: boolean,
|
||||
_isScreenShare: boolean;
|
||||
|
||||
/**
|
||||
* Indicates whether the thumbnail is for a virtual screenshare participant.
|
||||
*/
|
||||
_isVirtualScreenshare: boolean,
|
||||
_isVirtualScreenshare: boolean;
|
||||
|
||||
/**
|
||||
* Indicates whether the participant is local.
|
||||
*/
|
||||
_local: boolean,
|
||||
_local?: boolean;
|
||||
|
||||
/**
|
||||
* Shared video local participant owner.
|
||||
*/
|
||||
_localVideoOwner: boolean,
|
||||
_localVideoOwner: boolean;
|
||||
|
||||
/**
|
||||
* The ID of the participant obtain from the participant object in Redux.
|
||||
@@ -87,71 +89,71 @@ type Props = {
|
||||
* NOTE: Generally it should be the same as the participantID prop except the case where the passed
|
||||
* participantID doesn't correspond to any of the existing participants.
|
||||
*/
|
||||
_participantId: string,
|
||||
_participantId: string;
|
||||
|
||||
/**
|
||||
* Indicates whether the participant is pinned or not.
|
||||
*/
|
||||
_pinned: boolean,
|
||||
_pinned?: boolean;
|
||||
|
||||
/**
|
||||
* Whether or not the participant has the hand raised.
|
||||
*/
|
||||
_raisedHand: boolean,
|
||||
_raisedHand: boolean;
|
||||
|
||||
/**
|
||||
* Whether to show the dominant speaker indicator or not.
|
||||
*/
|
||||
_renderDominantSpeakerIndicator: boolean,
|
||||
_renderDominantSpeakerIndicator?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to show the moderator indicator or not.
|
||||
*/
|
||||
_renderModeratorIndicator: boolean,
|
||||
_renderModeratorIndicator: boolean;
|
||||
|
||||
/**
|
||||
* The video track that will be displayed in the thumbnail.
|
||||
*/
|
||||
_videoTrack: ?Object,
|
||||
_videoTrack?: ITrack;
|
||||
|
||||
/**
|
||||
* Invoked to trigger state changes in Redux.
|
||||
*/
|
||||
dispatch: Dispatch<any>,
|
||||
dispatch: IStore['dispatch'];
|
||||
|
||||
/**
|
||||
* The height of the thumnail.
|
||||
* The height of the thumbnail.
|
||||
*/
|
||||
height: ?number,
|
||||
height?: number;
|
||||
|
||||
/**
|
||||
* The ID of the participant related to the thumbnail.
|
||||
*/
|
||||
participantID: ?string,
|
||||
participantID?: string;
|
||||
|
||||
/**
|
||||
* Whether to display or hide the display name of the participant in the thumbnail.
|
||||
*/
|
||||
renderDisplayName: ?boolean,
|
||||
renderDisplayName?: boolean;
|
||||
|
||||
/**
|
||||
* If true, it tells the thumbnail that it needs to behave differently. E.g. React differently to a single tap.
|
||||
*/
|
||||
tileView?: boolean
|
||||
};
|
||||
tileView?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* React component for video thumbnail.
|
||||
*/
|
||||
class Thumbnail extends PureComponent<Props> {
|
||||
class Thumbnail extends PureComponent<IProps> {
|
||||
|
||||
/**
|
||||
* Creates new Thumbnail component.
|
||||
*
|
||||
* @param {Props} props - The props of the component.
|
||||
* @param {IProps} props - The props of the component.
|
||||
* @returns {Thumbnail}
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this._onClick = this._onClick.bind(this);
|
||||
@@ -159,8 +161,6 @@ class Thumbnail extends PureComponent<Props> {
|
||||
this.handleTrackStreamingStatusChanged = this.handleTrackStreamingStatusChanged.bind(this);
|
||||
}
|
||||
|
||||
_onClick: () => void;
|
||||
|
||||
/**
|
||||
* Thumbnail click handler.
|
||||
*
|
||||
@@ -176,8 +176,6 @@ class Thumbnail extends PureComponent<Props> {
|
||||
}
|
||||
}
|
||||
|
||||
_onThumbnailLongPress: () => void;
|
||||
|
||||
/**
|
||||
* Thumbnail long press handler.
|
||||
*
|
||||
@@ -219,11 +217,11 @@ class Thumbnail extends PureComponent<Props> {
|
||||
if (!_fakeParticipant || _isVirtualScreenshare) {
|
||||
indicators.push(<View
|
||||
key = 'top-left-indicators'
|
||||
style = { styles.thumbnailTopLeftIndicatorContainer }>
|
||||
style = { styles.thumbnailTopLeftIndicatorContainer as ViewStyle }>
|
||||
{ !_isVirtualScreenshare && <ConnectionIndicator participantId = { participantId } /> }
|
||||
{ !_isVirtualScreenshare && <RaisedHandIndicator participantId = { participantId } /> }
|
||||
{ tileView && (isScreenShare || _isVirtualScreenshare) && (
|
||||
<View style = { styles.screenShareIndicatorContainer }>
|
||||
<View style = { styles.screenShareIndicatorContainer as ViewStyle }>
|
||||
<ScreenShareIndicator />
|
||||
</View>
|
||||
) }
|
||||
@@ -231,7 +229,9 @@ class Thumbnail extends PureComponent<Props> {
|
||||
indicators.push(<Container
|
||||
key = 'bottom-indicators'
|
||||
style = { styles.thumbnailIndicatorContainer }>
|
||||
<Container style = { (audioMuted || renderModeratorIndicator) && styles.bottomIndicatorsContainer }>
|
||||
<Container
|
||||
style = { ((audioMuted || renderModeratorIndicator) && styles.bottomIndicatorsContainer
|
||||
) as StyleType }>
|
||||
{ audioMuted && !_isVirtualScreenshare && <AudioMutedIndicator /> }
|
||||
{ !tileView && _pinned && <PinnedIndicator />}
|
||||
{ renderModeratorIndicator && !_isVirtualScreenshare && <ModeratorIndicator />}
|
||||
@@ -275,7 +275,7 @@ class Thumbnail extends PureComponent<Props> {
|
||||
* @inheritdoc
|
||||
* @returns {void}
|
||||
*/
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
componentDidUpdate(prevProps: IProps) {
|
||||
// TODO: after converting this component to a react function component,
|
||||
// use a custom hook to update local track streaming status.
|
||||
const { _videoTrack, dispatch } = this.props;
|
||||
@@ -323,7 +323,7 @@ class Thumbnail extends PureComponent<Props> {
|
||||
* @param {JitsiTrackStreamingStatus} streamingStatus - The updated track streaming status.
|
||||
* @returns {void}
|
||||
*/
|
||||
handleTrackStreamingStatusChanged(jitsiTrack, streamingStatus) {
|
||||
handleTrackStreamingStatusChanged(jitsiTrack: any, streamingStatus: string) {
|
||||
this.props.dispatch(trackStreamingStatusChanged(jitsiTrack, streamingStatus));
|
||||
}
|
||||
|
||||
@@ -363,11 +363,11 @@ class Thumbnail extends PureComponent<Props> {
|
||||
styleOverrides,
|
||||
_raisedHand && !_isVirtualScreenshare ? styles.thumbnailRaisedHand : null,
|
||||
_renderDominantSpeakerIndicator && !_isVirtualScreenshare ? styles.thumbnailDominantSpeaker : null
|
||||
] }
|
||||
] as StyleType[] }
|
||||
touchFeedback = { false }>
|
||||
{ _gifSrc ? <Image
|
||||
source = {{ uri: _gifSrc }}
|
||||
style = { styles.thumbnailGif } />
|
||||
style = { styles.thumbnailGif as ImageStyle } />
|
||||
: <>
|
||||
<ParticipantView
|
||||
avatarSize = { tileView ? AVATAR_SIZE * 1.5 : AVATAR_SIZE }
|
||||
@@ -388,36 +388,36 @@ class Thumbnail extends PureComponent<Props> {
|
||||
* Function that maps parts of Redux state tree into component props.
|
||||
*
|
||||
* @param {Object} state - Redux state.
|
||||
* @param {Props} ownProps - Properties of component.
|
||||
* @param {IProps} ownProps - Properties of component.
|
||||
* @returns {Object}
|
||||
*/
|
||||
function _mapStateToProps(state, ownProps) {
|
||||
function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
const { ownerId } = state['features/shared-video'];
|
||||
const tracks = state['features/base/tracks'];
|
||||
const { participantID, tileView } = ownProps;
|
||||
const participant = getParticipantByIdOrUndefined(state, participantID);
|
||||
const localParticipantId = getLocalParticipant(state).id;
|
||||
const localParticipantId = getLocalParticipant(state)?.id;
|
||||
const id = participant?.id;
|
||||
const audioTrack = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.AUDIO, id);
|
||||
const videoTrack = getVideoTrackByParticipant(state, participant);
|
||||
const isScreenShare = videoTrack?.videoType === VIDEO_TYPE.DESKTOP;
|
||||
const participantCount = getParticipantCount(state);
|
||||
const renderDominantSpeakerIndicator = participant && participant.dominantSpeaker && participantCount > 2;
|
||||
const renderDominantSpeakerIndicator = participant?.dominantSpeaker && participantCount > 2;
|
||||
const _isEveryoneModerator = isEveryoneModerator(state);
|
||||
const renderModeratorIndicator = tileView && !_isEveryoneModerator
|
||||
&& participant?.role === PARTICIPANT_ROLE.MODERATOR;
|
||||
const { gifUrl: gifSrc } = getGifForParticipant(state, id);
|
||||
const { gifUrl: gifSrc } = getGifForParticipant(state, id ?? '');
|
||||
const mode = getGifDisplayMode(state);
|
||||
|
||||
return {
|
||||
_audioMuted: audioTrack?.muted ?? true,
|
||||
_fakeParticipant: participant?.fakeParticipant,
|
||||
_gifSrc: mode === 'chat' ? null : gifSrc,
|
||||
_gifSrc: mode === 'chat' ? undefined : gifSrc,
|
||||
_isScreenShare: isScreenShare,
|
||||
_isVirtualScreenshare: isScreenShareParticipant(participant),
|
||||
_local: participant?.local,
|
||||
_localVideoOwner: Boolean(ownerId === localParticipantId),
|
||||
_participantId: id,
|
||||
_participantId: id ?? '',
|
||||
_pinned: participant?.pinned,
|
||||
_raisedHand: hasRaisedHand(participant),
|
||||
_renderDominantSpeakerIndicator: renderDominantSpeakerIndicator,
|
||||
@@ -1,16 +1,17 @@
|
||||
// @flow
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
import {
|
||||
FlatList,
|
||||
GestureResponderEvent,
|
||||
SafeAreaView,
|
||||
TouchableWithoutFeedback
|
||||
TouchableWithoutFeedback,
|
||||
ViewToken
|
||||
} from 'react-native';
|
||||
import { withSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { EdgeInsets, withSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { connect } from 'react-redux';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { IReduxState, IStore } from '../../../app/types';
|
||||
import { getLocalParticipant, getParticipantCountWithFake } from '../../../base/participants/functions';
|
||||
import { ILocalParticipant } from '../../../base/participants/types';
|
||||
import { getHideSelfView } from '../../../base/settings/functions.any';
|
||||
import { setVisibleRemoteParticipants } from '../../actions.web';
|
||||
|
||||
@@ -20,74 +21,74 @@ import styles from './styles';
|
||||
/**
|
||||
* The type of the React {@link Component} props of {@link TileView}.
|
||||
*/
|
||||
type Props = {
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* Application's aspect ratio.
|
||||
*/
|
||||
_aspectRatio: Symbol,
|
||||
_aspectRatio: Symbol;
|
||||
|
||||
/**
|
||||
* The number of columns.
|
||||
*/
|
||||
_columns: number,
|
||||
_columns: number;
|
||||
|
||||
/**
|
||||
* Whether or not to hide the self view.
|
||||
*/
|
||||
_disableSelfView: boolean,
|
||||
_disableSelfView: boolean;
|
||||
|
||||
/**
|
||||
* Application's viewport height.
|
||||
*/
|
||||
_height: number,
|
||||
_height: number;
|
||||
|
||||
/**
|
||||
* The local participant.
|
||||
*/
|
||||
_localParticipant: Object,
|
||||
_localParticipant?: ILocalParticipant;
|
||||
|
||||
/**
|
||||
* The number of participants in the conference.
|
||||
*/
|
||||
_participantCount: number,
|
||||
_participantCount: number;
|
||||
|
||||
/**
|
||||
* An array with the IDs of the remote participants in the conference.
|
||||
*/
|
||||
_remoteParticipants: Array<string>,
|
||||
_remoteParticipants: Array<string>;
|
||||
|
||||
/**
|
||||
* The thumbnail height.
|
||||
*/
|
||||
_thumbnailHeight: number,
|
||||
_thumbnailHeight?: number;
|
||||
|
||||
/**
|
||||
* Application's viewport height.
|
||||
*/
|
||||
_width: number,
|
||||
_width: number;
|
||||
|
||||
/**
|
||||
* Invoked to update the receiver video quality.
|
||||
*/
|
||||
dispatch: Dispatch<any>,
|
||||
dispatch: IStore['dispatch'];
|
||||
|
||||
/**
|
||||
* Object containing the safe area insets.
|
||||
*/
|
||||
insets: Object,
|
||||
insets: EdgeInsets;
|
||||
|
||||
/**
|
||||
* Callback to invoke when tile view is tapped.
|
||||
*/
|
||||
onClick: Function
|
||||
};
|
||||
onClick: (e?: GestureResponderEvent) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* An empty array. The purpose of the constant is to use the same reference every time we need an empty array.
|
||||
* This will prevent unnecessary re-renders.
|
||||
*/
|
||||
const EMPTY_ARRAY = [];
|
||||
const EMPTY_ARRAY: any[] = [];
|
||||
|
||||
/**
|
||||
* Implements a React {@link PureComponent} which displays thumbnails in a two
|
||||
@@ -95,17 +96,17 @@ const EMPTY_ARRAY = [];
|
||||
*
|
||||
* @augments PureComponent
|
||||
*/
|
||||
class TileView extends PureComponent<Props> {
|
||||
class TileView extends PureComponent<IProps> {
|
||||
|
||||
/**
|
||||
* The styles for the content container of the FlatList.
|
||||
*/
|
||||
_contentContainerStyles: Object;
|
||||
_contentContainerStyles: any;
|
||||
|
||||
/**
|
||||
* The styles for the FlatList.
|
||||
*/
|
||||
_flatListStyles: Object;
|
||||
_flatListStyles: any;
|
||||
|
||||
/**
|
||||
* The FlatList's viewabilityConfig.
|
||||
@@ -115,9 +116,9 @@ class TileView extends PureComponent<Props> {
|
||||
/**
|
||||
* Creates new TileView 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._keyExtractor = this._keyExtractor.bind(this);
|
||||
@@ -137,20 +138,16 @@ class TileView extends PureComponent<Props> {
|
||||
};
|
||||
}
|
||||
|
||||
_keyExtractor: string => string;
|
||||
|
||||
/**
|
||||
* Returns a key for a passed item of the list.
|
||||
*
|
||||
* @param {string} item - The user ID.
|
||||
* @returns {string} - The user ID.
|
||||
*/
|
||||
_keyExtractor(item) {
|
||||
_keyExtractor(item: string) {
|
||||
return item;
|
||||
}
|
||||
|
||||
_onViewableItemsChanged: Object => void;
|
||||
|
||||
/**
|
||||
* A handler for visible items changes.
|
||||
*
|
||||
@@ -158,7 +155,7 @@ class TileView extends PureComponent<Props> {
|
||||
* @param {Array<Object>} data.viewableItems - The visible items array.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onViewableItemsChanged({ viewableItems = [] }: { viewableItems: Array<Object> }) {
|
||||
_onViewableItemsChanged({ viewableItems = [] }: { viewableItems: ViewToken[]; }) {
|
||||
const { _disableSelfView } = this.props;
|
||||
|
||||
if (viewableItems[0]?.index === 0 && !_disableSelfView) {
|
||||
@@ -172,8 +169,8 @@ class TileView extends PureComponent<Props> {
|
||||
}
|
||||
|
||||
// We are off by one in the remote participants array.
|
||||
const startIndex = viewableItems[0].index - (_disableSelfView ? 0 : 1);
|
||||
const endIndex = viewableItems[viewableItems.length - 1].index - (_disableSelfView ? 0 : 1);
|
||||
const startIndex = Number(viewableItems[0].index) - (_disableSelfView ? 0 : 1);
|
||||
const endIndex = Number(viewableItems[viewableItems.length - 1].index) - (_disableSelfView ? 0 : 1);
|
||||
|
||||
this.props.dispatch(setVisibleRemoteParticipants(startIndex, endIndex));
|
||||
}
|
||||
@@ -187,7 +184,7 @@ class TileView extends PureComponent<Props> {
|
||||
render() {
|
||||
const { _columns, _height, _thumbnailHeight, _width, onClick } = this.props;
|
||||
const participants = this._getSortedParticipants();
|
||||
const initialRowsToRender = Math.ceil(_height / (_thumbnailHeight + (2 * styles.thumbnail.margin)));
|
||||
const initialRowsToRender = Math.ceil(_height / (Number(_thumbnailHeight) + (2 * styles.thumbnail.margin)));
|
||||
|
||||
if (this._flatListStyles.minHeight !== _height || this._flatListStyles.minWidth !== _width) {
|
||||
this._flatListStyles = {
|
||||
@@ -250,15 +247,13 @@ class TileView extends PureComponent<Props> {
|
||||
return [ _localParticipant?.id, ..._remoteParticipants ];
|
||||
}
|
||||
|
||||
_renderThumbnail: Object => Object;
|
||||
|
||||
/**
|
||||
* Creates React Element to display each participant in a thumbnail.
|
||||
*
|
||||
* @private
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderThumbnail({ item/* , index , separators */ }) {
|
||||
_renderThumbnail({ item }: { item: string; }) {
|
||||
const { _thumbnailHeight } = this.props;
|
||||
|
||||
return (
|
||||
@@ -278,18 +273,18 @@ class TileView extends PureComponent<Props> {
|
||||
* @param {Object} state - The redux state.
|
||||
* @param {Object} ownProps - Component props.
|
||||
* @private
|
||||
* @returns {Props}
|
||||
* @returns {IProps}
|
||||
*/
|
||||
function _mapStateToProps(state, ownProps) {
|
||||
function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
const responsiveUi = state['features/base/responsive-ui'];
|
||||
const { remoteParticipants, tileViewDimensions } = state['features/filmstrip'];
|
||||
const disableSelfView = getHideSelfView(state);
|
||||
const { height } = tileViewDimensions.thumbnailSize;
|
||||
const { columns } = tileViewDimensions;
|
||||
const { height } = tileViewDimensions?.thumbnailSize ?? {};
|
||||
const { columns } = tileViewDimensions ?? {};
|
||||
|
||||
return {
|
||||
_aspectRatio: responsiveUi.aspectRatio,
|
||||
_columns: columns,
|
||||
_columns: columns ?? 1,
|
||||
_disableSelfView: disableSelfView,
|
||||
_height: responsiveUi.clientHeight - (ownProps.insets?.top || 0),
|
||||
_insets: ownProps.insets,
|
||||
@@ -184,6 +184,7 @@ interface IDimensions {
|
||||
}
|
||||
|
||||
interface IFilmstripDimensions {
|
||||
columns?: number;
|
||||
filmstripHeight?: number;
|
||||
filmstripWidth?: number;
|
||||
gridDimensions?: {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { GiphyContent, GiphyGridView, GiphyMediaType } from '@giphy/react-native-sdk';
|
||||
import { GiphyContent, GiphyGridView, GiphyMediaType, GiphyRating } from '@giphy/react-native-sdk';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
@@ -9,7 +9,7 @@ import JitsiScreen from '../../../base/modal/components/JitsiScreen';
|
||||
import Input from '../../../base/ui/components/native/Input';
|
||||
import { sendMessage } from '../../../chat/actions.any';
|
||||
import { goBack } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
import { formatGifUrlMessage, getGifRating, getGifUrl, getGiphyProxyUrl } from '../../functions';
|
||||
import { formatGifUrlMessage, getGifRating, getGifUrl, getGiphyProxyUrl } from '../../functions.native';
|
||||
|
||||
import GifsMenuFooter from './GifsMenuFooter';
|
||||
import styles from './styles';
|
||||
@@ -18,7 +18,7 @@ const GifsMenu = () => {
|
||||
const [ searchQuery, setSearchQuery ] = useState('');
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
const rating = useSelector(getGifRating);
|
||||
const rating = useSelector(getGifRating) as GiphyRating;
|
||||
const proxyUrl = useSelector(getGiphyProxyUrl);
|
||||
|
||||
const options = {
|
||||
@@ -1,8 +1,7 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Image, Text, View } from 'react-native';
|
||||
import { Image, Text, TextStyle, View, ViewStyle } from 'react-native';
|
||||
|
||||
// @ts-ignore
|
||||
import styles from './styles';
|
||||
|
||||
|
||||
@@ -15,9 +14,10 @@ const GifsMenuFooter = (): JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<View style = { styles.credit }>
|
||||
<Text
|
||||
style = { styles.creditText }>{ t('poweredby') }</Text>
|
||||
<View style = { styles.credit as ViewStyle }>
|
||||
<Text style = { styles.creditText as TextStyle }>
|
||||
{ t('poweredby') }
|
||||
</Text>
|
||||
<Image
|
||||
source = { require('../../../../../images/GIPHY_logo.png') } />
|
||||
</View>
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
|
||||
/**
|
||||
* {@code AbstractGoogleSignInButton} Component's property types.
|
||||
*/
|
||||
interface IProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
* The callback to invoke when the button is clicked.
|
||||
*/
|
||||
onClick: (e?: React.MouseEvent) => void;
|
||||
|
||||
/**
|
||||
* True if the user is signed in, so it needs to render a different label
|
||||
* and maybe different style (for the future).
|
||||
*/
|
||||
signedIn?: boolean;
|
||||
|
||||
/**
|
||||
* The text to display within {@code GoogleSignInButton}.
|
||||
*/
|
||||
text?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract class of the {@code GoogleSignInButton} to share platform
|
||||
* independent code.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
export default class AbstractGoogleSignInButton extends Component<IProps> {
|
||||
}
|
||||
@@ -1,13 +1,11 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { Image, TouchableOpacity } from 'react-native';
|
||||
import React, { Component } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { GestureResponderEvent, Image, ImageStyle, TouchableOpacity, ViewStyle } from 'react-native';
|
||||
|
||||
import { translate } from '../../base/i18n/functions';
|
||||
import Button from '../../base/ui/components/native/Button';
|
||||
import { BUTTON_TYPES } from '../../base/ui/constants.native';
|
||||
|
||||
import AbstractGoogleSignInButton from './AbstractGoogleSignInButton';
|
||||
import styles from './styles';
|
||||
|
||||
// eslint-disable-next-line
|
||||
@@ -21,12 +19,31 @@ const GOOGLE_BRAND_IMAGE = require('../../../../images/btn_google_signin_dark_no
|
||||
* this way), hence the custom button implementation.
|
||||
*/
|
||||
|
||||
interface IProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
* The callback to invoke when the button is clicked.
|
||||
*/
|
||||
onClick: (e?: React.MouseEvent<HTMLButtonElement> | GestureResponderEvent) => void;
|
||||
|
||||
/**
|
||||
* True if the user is signed in, so it needs to render a different label
|
||||
* and maybe different style (for the future).
|
||||
*/
|
||||
signedIn?: boolean;
|
||||
|
||||
/**
|
||||
* The text to display within {@code GoogleSignInButton}.
|
||||
*/
|
||||
text?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A React Component showing a button to sign in with Google.
|
||||
*
|
||||
* @augments Component
|
||||
*/
|
||||
class GoogleSignInButton extends AbstractGoogleSignInButton {
|
||||
class GoogleSignInButton extends Component<IProps> {
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
@@ -51,11 +68,11 @@ class GoogleSignInButton extends AbstractGoogleSignInButton {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
onPress = { onClick }
|
||||
style = { styles.signInButton } >
|
||||
style = { styles.signInButton as ViewStyle } >
|
||||
<Image
|
||||
resizeMode = { 'contain' }
|
||||
source = { GOOGLE_BRAND_IMAGE }
|
||||
style = { styles.signInImage } />
|
||||
style = { styles.signInImage as ImageStyle } />
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
@@ -1,15 +1,33 @@
|
||||
import React from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
|
||||
import { translate } from '../../base/i18n/functions';
|
||||
|
||||
import AbstractGoogleSignInButton from './AbstractGoogleSignInButton';
|
||||
interface IProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
* The callback to invoke when the button is clicked.
|
||||
*/
|
||||
onClick: (e?: React.MouseEvent) => void;
|
||||
|
||||
/**
|
||||
* True if the user is signed in, so it needs to render a different label
|
||||
* and maybe different style (for the future).
|
||||
*/
|
||||
signedIn?: boolean;
|
||||
|
||||
/**
|
||||
* The text to display within {@code GoogleSignInButton}.
|
||||
*/
|
||||
text?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A React Component showing a button to sign in with Google.
|
||||
*
|
||||
* @augments Component
|
||||
*/
|
||||
class GoogleSignInButton extends AbstractGoogleSignInButton {
|
||||
class GoogleSignInButton extends Component<IProps> {
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
|
||||
import { createStyleSheet } from '../../base/styles/functions.any';
|
||||
|
||||
/**
|
||||
Reference in New Issue
Block a user