fix(participants-pane) Make search work with breakout rooms (#10668)
Web and native
This commit is contained in:
parent
b9e182b7cc
commit
9816be4745
|
@ -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>
|
||||
|
|
|
@ -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 } />
|
||||
))
|
||||
}
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue