fix(participants-pane) Make search work with breakout rooms (#10668)

Web and native
This commit is contained in:
Robert Pintilii 2021-12-21 14:51:39 +02:00 committed by GitHub
parent b9e182b7cc
commit 9816be4745
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 121 additions and 74 deletions

View File

@ -7,6 +7,7 @@ import { useDispatch } from 'react-redux';
import { openDialog } from '../../../base/dialog';
import { Icon, IconArrowDown, IconArrowUp } from '../../../base/icons';
import { participantMatchesSearch } from '../../../participants-pane/functions';
import BreakoutRoomContextMenu from './BreakoutRoomContextMenu';
import BreakoutRoomParticipantItem from './BreakoutRoomParticipantItem';
@ -17,7 +18,12 @@ type Props = {
/**
* Room to display.
*/
room: Object
room: Object,
/**
* Participants search string.
*/
searchString: string
}
/**
@ -31,7 +37,7 @@ function _keyExtractor(item: Object) {
}
export const CollapsibleRoom = ({ room }: Props) => {
export const CollapsibleRoom = ({ room, searchString }: Props) => {
const dispatch = useDispatch();
const [ collapsed, setCollapsed ] = useState(false);
const { t } = useTranslation();
@ -65,7 +71,8 @@ export const CollapsibleRoom = ({ room }: Props) => {
horizontal = { false }
keyExtractor = { _keyExtractor }
// eslint-disable-next-line react/jsx-no-bind
renderItem = { ({ item: participant }) => <BreakoutRoomParticipantItem item = { participant } /> }
renderItem = { ({ item: participant }) => participantMatchesSearch(participant, searchString)
&& <BreakoutRoomParticipantItem item = { participant } /> }
showsHorizontalScrollIndicator = { false }
windowSize = { 2 } />}
</View>

View File

@ -10,6 +10,7 @@ import { ListItem } from '../../../base/components';
import { Icon, IconArrowDown, IconArrowUp } from '../../../base/icons';
import ParticipantItem from '../../../participants-pane/components/web/ParticipantItem';
import { ACTION_TRIGGER } from '../../../participants-pane/constants';
import { participantMatchesSearch } from '../../../participants-pane/functions';
type Props = {
@ -42,6 +43,11 @@ type Props = {
* Room reference.
*/
room: Object,
/**
* Participants search string.
*/
searchString: string
}
const useStyles = makeStyles(theme => {
@ -78,7 +84,8 @@ export const CollapsibleRoom = ({
isHighlighted,
onRaiseMenu,
onLeave,
room
room,
searchString
}: Props) => {
const { t } = useTranslation();
const styles = useStyles();
@ -116,13 +123,14 @@ export const CollapsibleRoom = ({
textChildren = { roomName }
trigger = { actionsTrigger } />
{!collapsed && room?.participants
&& Object.values(room?.participants || {}).map((p: Object) => (
<ParticipantItem
displayName = { p.displayName || defaultRemoteDisplayName }
key = { p.jid }
local = { false }
participantID = { p.jid } />
))
&& Object.values(room?.participants || {}).map((p: Object) =>
participantMatchesSearch(p, searchString) && (
<ParticipantItem
displayName = { p.displayName || defaultRemoteDisplayName }
key = { p.jid }
local = { false }
participantID = { p.jid } />
))
}
</>
);

View File

@ -16,7 +16,15 @@ import { LeaveButton } from './LeaveButton';
import RoomActionEllipsis from './RoomActionEllipsis';
import { RoomContextMenu } from './RoomContextMenu';
export const RoomList = () => {
type Props = {
/**
* Participants search string.
*/
searchString: string
}
export const RoomList = ({ searchString }: Props) => {
const currentRoomId = useSelector(getCurrentRoomId);
const rooms = Object.values(useSelector(getBreakoutRooms, equals))
.filter((room: Object) => room.id !== currentRoomId)
@ -44,7 +52,8 @@ export const RoomList = () => {
isHighlighted = { raiseContext.entity === room }
onLeave = { lowerMenu }
onRaiseMenu = { onRaiseMenu(room) }
room = { room }>
room = { room }
searchString = { searchString }>
{!_overflowDrawer && <>
<JoinActionButton room = { room } />
{isLocalModerator && !room.isMainRoom

View File

@ -8,10 +8,9 @@ import { translate } from '../../../base/i18n';
import { Icon, IconInviteMore } from '../../../base/icons';
import { getLocalParticipant, getParticipantCountWithFake, getRemoteParticipants } from '../../../base/participants';
import { connect } from '../../../base/redux';
import { normalizeAccents } from '../../../base/util/strings';
import { getBreakoutRooms, getCurrentRoomId } from '../../../breakout-rooms/functions';
import { doInvitePeople } from '../../../invite/actions.native';
import { shouldRenderInviteButton } from '../../functions';
import { participantMatchesSearch, shouldRenderInviteButton } from '../../functions';
import ClearableInput from './ClearableInput';
import MeetingParticipantItem from './MeetingParticipantItem';
@ -55,6 +54,16 @@ type Props = {
*/
dispatch: Function,
/**
* Participants search string.
*/
searchString: string,
/**
* Function to update the search string.
*/
setSearchString: Function,
/**
* Translation function.
*/
@ -66,14 +75,10 @@ type Props = {
theme: Object
}
type State = {
searchString: string
};
/**
* The meeting participant list component.
*/
class MeetingParticipantList extends PureComponent<Props, State> {
class MeetingParticipantList extends PureComponent<Props> {
/**
* Creates new MeetingParticipantList instance.
@ -83,10 +88,6 @@ class MeetingParticipantList extends PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
searchString: ''
};
this._keyExtractor = this._keyExtractor.bind(this);
this._onInvite = this._onInvite.bind(this);
this._renderParticipant = this._renderParticipant.bind(this);
@ -139,27 +140,10 @@ class MeetingParticipantList extends PureComponent<Props, State> {
* @returns {ReactElement}
*/
_renderParticipant({ item/* , index, separators */ }) {
const { _localParticipant, _remoteParticipants } = this.props;
const { searchString } = this.state;
const { _localParticipant, _remoteParticipants, searchString } = this.props;
const participant = item === _localParticipant?.id ? _localParticipant : _remoteParticipants.get(item);
const displayName = participant?.name || '';
if (displayName) {
const names = normalizeAccents(displayName)
.toLowerCase()
.split(' ');
const lowerCaseSearch = normalizeAccents(searchString).toLowerCase();
for (const name of names) {
if (lowerCaseSearch === '' || name.startsWith(lowerCaseSearch)) {
return (
<MeetingParticipantItem
key = { item }
participant = { participant } />
);
}
}
} else if (displayName === '' && searchString === '') {
if (participantMatchesSearch(participant, searchString)) {
return (
<MeetingParticipantItem
key = { item }
@ -179,9 +163,7 @@ class MeetingParticipantList extends PureComponent<Props, State> {
* @returns {void}
*/
_onSearchStringChange(text: string) {
this.setState({
searchString: text
});
this.props.setSearchString(text);
}
/**

View File

@ -1,6 +1,6 @@
// @flow
import React, { useCallback } from 'react';
import React, { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { ScrollView, View } from 'react-native';
import { Button } from 'react-native-paper';
@ -36,6 +36,7 @@ import styles from './styles';
*/
const ParticipantsPane = () => {
const dispatch = useDispatch();
const [ searchString, setSearchString ] = useState('');
const openMoreMenu = useCallback(() => dispatch(openDialog(ContextMenuMore)), [ dispatch ]);
const isLocalModerator = useSelector(isLocalParticipantModerator);
const muteAll = useCallback(() => dispatch(openDialog(MuteEveryoneDialog)),
@ -59,7 +60,9 @@ const ParticipantsPane = () => {
style = { styles.participantsPane }>
<ScrollView bounces = { false }>
<LobbyParticipantList />
<MeetingParticipantList />
<MeetingParticipantList
searchString = { searchString }
setSearchString = { setSearchString } />
{!inBreakoutRoom
&& isLocalModerator
&& participantsCount > 2
@ -69,7 +72,8 @@ const ParticipantsPane = () => {
{_isBreakoutRoomsSupported
&& rooms.map(room => (<CollapsibleRoom
key = { room.id }
room = { room } />))}
room = { room }
searchString = { searchString } />))}
{_isBreakoutRoomsSupported && !hideAddRoomButton && isLocalModerator
&& <AddBreakoutRoomButton />}
</ScrollView>

View File

@ -19,12 +19,12 @@ import {
isParticipantAudioMuted,
isParticipantVideoMuted
} from '../../../base/tracks';
import { normalizeAccents } from '../../../base/util/strings';
import { ACTION_TRIGGER, type MediaState, MEDIA_STATE } from '../../constants';
import {
getParticipantAudioMediaState,
getParticipantVideoMediaState,
getQuickActionButtonType
getQuickActionButtonType,
participantMatchesSearch
} from '../../functions';
import ParticipantActionEllipsis from './ParticipantActionEllipsis';
@ -300,22 +300,7 @@ function _mapStateToProps(state, ownProps): Object {
const _displayName = getParticipantDisplayName(state, participant?.id);
let _matchesSearch = false;
const names = normalizeAccents(_displayName)
.toLowerCase()
.split(' ');
const lowerCaseSearchString = searchString.toLowerCase();
if (lowerCaseSearchString === '') {
_matchesSearch = true;
} else {
for (const name of names) {
if (name.startsWith(lowerCaseSearchString)) {
_matchesSearch = true;
break;
}
}
}
const _matchesSearch = participantMatchesSearch(participant, searchString);
const _isAudioMuted = isParticipantAudioMuted(participant, state);
const _isVideoMuted = isParticipantVideoMuted(participant, state);

View File

@ -1,7 +1,7 @@
// @flow
import { makeStyles } from '@material-ui/styles';
import React, { useCallback, useState } from 'react';
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
@ -45,8 +45,10 @@ const useStyles = makeStyles(theme => {
type Props = {
currentRoom: ?Object,
participantsCount: number,
showInviteButton: boolean,
overflowDrawer: boolean,
searchString: string,
setSearchString: Function,
showInviteButton: boolean,
sortedParticipantIds: Array<string>
};
@ -64,11 +66,12 @@ function MeetingParticipants({
currentRoom,
overflowDrawer,
participantsCount,
searchString,
setSearchString,
showInviteButton,
sortedParticipantIds = []
}: Props) {
const dispatch = useDispatch();
const [ searchString, setSearchString ] = useState('');
const { t } = useTranslation();
const [ lowerMenu, , toggleMenu, menuEnter, menuLeave, raiseContext ] = useContextMenu();

View File

@ -75,6 +75,11 @@ type State = {
* Indicates if the footer context menu is open.
*/
contextOpen: boolean,
/**
* Participants search string.
*/
searchString: string
};
const styles = theme => {
@ -152,7 +157,8 @@ class ParticipantsPane extends Component<Props, State> {
super(props);
this.state = {
contextOpen: false
contextOpen: false,
searchString: ''
};
// Bind event handlers so they are only bound once per instance.
@ -162,6 +168,7 @@ class ParticipantsPane extends Component<Props, State> {
this._onMuteAll = this._onMuteAll.bind(this);
this._onToggleContext = this._onToggleContext.bind(this);
this._onWindowClickListener = this._onWindowClickListener.bind(this);
this.setSearchString = this.setSearchString.bind(this);
}
@ -197,7 +204,7 @@ class ParticipantsPane extends Component<Props, State> {
classes,
t
} = this.props;
const { contextOpen } = this.state;
const { contextOpen, searchString } = this.state;
// when the pane is not open optimize to not
// execute the MeetingParticipantList render for large list of participants
@ -224,8 +231,10 @@ class ParticipantsPane extends Component<Props, State> {
<div className = { classes.container }>
<LobbyParticipants />
<br className = { classes.antiCollapse } />
<MeetingParticipants />
{_isBreakoutRoomsSupported && <RoomList />}
<MeetingParticipants
searchString = { searchString }
setSearchString = { this.setSearchString } />
{_isBreakoutRoomsSupported && <RoomList searchString = { searchString } />}
{_showAddRoomButton && <AddBreakoutRoomButton />}
</div>
{_showFooter && (
@ -255,6 +264,20 @@ class ParticipantsPane extends Component<Props, State> {
);
}
setSearchString: (string) => void;
/**
* Sets the search string.
*
* @param {string} newSearchString - The new search string.
* @returns {void}
*/
setSearchString(newSearchString) {
this.setState({
searchString: newSearchString
});
}
_onClosePane: () => void;
/**

View File

@ -17,6 +17,7 @@ import {
getRaiseHandsQueue
} from '../base/participants/functions';
import { toState } from '../base/redux';
import { normalizeAccents } from '../base/util/strings';
import { isInBreakoutRoom } from '../breakout-rooms/functions';
import { QUICK_ACTION_BUTTON, REDUCER_KEY, MEDIA_STATE } from './constants';
@ -242,3 +243,28 @@ export function getSortedParticipantIds(stateful: Object | Function): Array<stri
];
}
/**
* Checks if a participant matches the search string.
*
* @param {Object} participant - The participant to be checked.
* @param {string} searchString - The participants search string.
* @returns {boolean}
*/
export function participantMatchesSearch(participant: Object, searchString: string) {
if (searchString === '') {
return true;
}
const names = normalizeAccents(participant?.name || participant?.displayName || '')
.toLowerCase()
.split(' ');
const lowerCaseSearchString = searchString.toLowerCase();
for (const name of names) {
if (name.startsWith(lowerCaseSearchString)) {
return true;
}
}
return false;
}