feat: add custom buttons for participant menu and toolbar via config (#12832)
* add custom remote menu button * add config for custom buttons * whitelist custom buttons flag * add toolbox custom button * fix notify toolbox buttons * whitelist toolbar custom buttons * rename and fix notify * rename participant remote menu * revert some flag wrong changes * fix some formatings * add undefined type to custom buttons toolbox * code review * code review 2 * fix linting issue
This commit is contained in:
parent
3a5833829c
commit
1a113ba733
|
@ -799,6 +799,14 @@ var config = {
|
|||
// 'microphone', 'camera', 'select-background', 'invite', 'settings'
|
||||
// hiddenPremeetingButtons: [],
|
||||
|
||||
// An array with custom option buttons for the participant context menu
|
||||
// type: Array<{ icon: string; id: string; text: string; }>
|
||||
// customParticipantMenuButtons: [],
|
||||
|
||||
// An array with custom option buttons for the toolbar
|
||||
// type: Array<{ icon: string; id: string; text: string; }>
|
||||
// customToolbarButtons: [],
|
||||
|
||||
// Stats
|
||||
//
|
||||
|
||||
|
|
|
@ -1941,6 +1941,21 @@ class API {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application ( if API is enabled) that a participant menu button was clicked.
|
||||
*
|
||||
* @param {string} key - The key of the participant menu button.
|
||||
* @param {string} participantId - The ID of the participant for with the participant menu button was clicked.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyParticipantMenuButtonClicked(key, participantId) {
|
||||
this._sendEvent({
|
||||
name: 'participant-menu-button-clicked',
|
||||
key,
|
||||
participantId
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes the allocated resources.
|
||||
*
|
||||
|
|
|
@ -140,6 +140,7 @@ const events = {
|
|||
'raise-hand-updated': 'raiseHandUpdated',
|
||||
'recording-link-available': 'recordingLinkAvailable',
|
||||
'recording-status-changed': 'recordingStatusChanged',
|
||||
'participant-menu-button-clicked': 'participantMenuButtonClick',
|
||||
'video-ready-to-close': 'readyToClose',
|
||||
'video-conference-joined': 'videoConferenceJoined',
|
||||
'video-conference-left': 'videoConferenceLeft',
|
||||
|
|
|
@ -206,6 +206,8 @@ export interface IConfig {
|
|||
};
|
||||
};
|
||||
corsAvatarURLs?: Array<string>;
|
||||
customParticipantMenuButtons?: Array<{ icon: string; id: string; text: string; }>;
|
||||
customToolbarButtons?: Array<{ icon: string; id: string; text: string; }>;
|
||||
deeplinking?: IDeeplinkingConfig;
|
||||
defaultLanguage?: string;
|
||||
defaultLocalDisplayName?: string;
|
||||
|
|
|
@ -32,9 +32,16 @@ export function getReplaceParticipant(state: IReduxState): string | undefined {
|
|||
* @returns {Array<string>} - The list of enabled toolbar buttons.
|
||||
*/
|
||||
export function getToolbarButtons(state: IReduxState): Array<string> {
|
||||
const { toolbarButtons } = state['features/base/config'];
|
||||
const { toolbarButtons, customToolbarButtons } = state['features/base/config'];
|
||||
const customButtons = customToolbarButtons?.map(({ id }) => id);
|
||||
|
||||
return Array.isArray(toolbarButtons) ? toolbarButtons : TOOLBAR_BUTTONS;
|
||||
const buttons = Array.isArray(toolbarButtons) ? toolbarButtons : TOOLBAR_BUTTONS;
|
||||
|
||||
if (customButtons) {
|
||||
buttons.push(...customButtons);
|
||||
}
|
||||
|
||||
return buttons;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -101,3 +108,30 @@ export function _setDeeplinkingDefaults(deeplinking: IDeeplinkingConfig) {
|
|||
android.dynamicLink.isi = android.dynamicLink.isi || '1165103905';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of buttons that have that notify the api when clicked.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {Array} - The list of buttons.
|
||||
*/
|
||||
export function getButtonsWithNotifyClick(state: IReduxState): Array<{ key: string; preventExecution: boolean; }> {
|
||||
const { buttonsWithNotifyClick, customToolbarButtons } = state['features/base/config'];
|
||||
const customButtons = customToolbarButtons?.map(({ id }) => {
|
||||
return {
|
||||
key: id,
|
||||
preventExecution: false
|
||||
};
|
||||
});
|
||||
|
||||
const buttons = Array.isArray(buttonsWithNotifyClick)
|
||||
? buttonsWithNotifyClick as Array<{ key: string; preventExecution: boolean; }>
|
||||
: [];
|
||||
|
||||
if (customButtons) {
|
||||
buttons.push(...customButtons);
|
||||
}
|
||||
|
||||
return buttons;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
import React from 'react';
|
||||
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
|
||||
|
||||
|
||||
type Props = AbstractButtonProps & {
|
||||
icon: string;
|
||||
text: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Component that renders a custom toolbox button.
|
||||
*
|
||||
* @returns {Component}
|
||||
*/
|
||||
class CustomOptionButton extends AbstractButton<Props, any, any> {
|
||||
// @ts-ignore
|
||||
iconSrc = this.props.icon;
|
||||
|
||||
// @ts-ignore
|
||||
id = this.props.id;
|
||||
|
||||
// @ts-ignore
|
||||
text = this.props.text;
|
||||
|
||||
accessibilityLabel = this.text;
|
||||
|
||||
/**
|
||||
* Custom icon component.
|
||||
*
|
||||
* @param {any} props - Icon's props.
|
||||
* @returns {img}
|
||||
*/
|
||||
icon = (props: any) => (<img
|
||||
src = { this.iconSrc }
|
||||
{ ...props } />);
|
||||
|
||||
label = this.text;
|
||||
tooltip = this.text;
|
||||
}
|
||||
|
||||
export default CustomOptionButton;
|
|
@ -12,6 +12,7 @@ import { sendAnalytics } from '../../../analytics/functions';
|
|||
import { IReduxState } from '../../../app/types';
|
||||
import { IJitsiConference } from '../../../base/conference/reducer';
|
||||
import {
|
||||
getButtonsWithNotifyClick,
|
||||
getMultipleVideoSendingSupportFeatureFlag,
|
||||
getToolbarButtons,
|
||||
isToolbarButtonEnabled
|
||||
|
@ -120,6 +121,7 @@ import HelpButton from '../HelpButton';
|
|||
|
||||
// @ts-ignore
|
||||
import AudioSettingsButton from './AudioSettingsButton';
|
||||
import CustomOptionButton from './CustomOptionButton';
|
||||
import { EndConferenceButton } from './EndConferenceButton';
|
||||
// @ts-ignore
|
||||
import FullscreenButton from './FullscreenButton';
|
||||
|
@ -174,6 +176,11 @@ interface IProps extends WithTranslation {
|
|||
*/
|
||||
_conference?: IJitsiConference;
|
||||
|
||||
/**
|
||||
* Custom Toolbar buttons.
|
||||
*/
|
||||
_customToolbarButtons?: Array<{ icon: string; id: string; text: string; }>;
|
||||
|
||||
/**
|
||||
* Whether or not screensharing button is disabled.
|
||||
*/
|
||||
|
@ -715,6 +722,7 @@ class Toolbox extends Component<IProps> {
|
|||
*/
|
||||
_getAllButtons() {
|
||||
const {
|
||||
_customToolbarButtons,
|
||||
_feedbackConfigured,
|
||||
_hasSalesforce,
|
||||
_isIosMobile,
|
||||
|
@ -915,6 +923,19 @@ class Toolbox extends Component<IProps> {
|
|||
group: 4
|
||||
};
|
||||
|
||||
const customButtons = _customToolbarButtons?.reduce((prev, { icon, id, text }) => {
|
||||
return {
|
||||
...prev,
|
||||
[id]: {
|
||||
key: id,
|
||||
Content: CustomOptionButton,
|
||||
group: 4,
|
||||
icon,
|
||||
text
|
||||
}
|
||||
};
|
||||
}, {});
|
||||
|
||||
return {
|
||||
microphone,
|
||||
camera,
|
||||
|
@ -945,7 +966,8 @@ class Toolbox extends Component<IProps> {
|
|||
embed,
|
||||
feedback,
|
||||
download,
|
||||
help
|
||||
help,
|
||||
...customButtons
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1525,8 +1547,8 @@ function _mapStateToProps(state: IReduxState, ownProps: Partial<IProps>) {
|
|||
const endConferenceSupported = conference?.isEndConferenceSupported() && isLocalParticipantModerator(state);
|
||||
|
||||
const {
|
||||
buttonsWithNotifyClick,
|
||||
callStatsID,
|
||||
customToolbarButtons,
|
||||
disableProfile,
|
||||
iAmRecorder,
|
||||
iAmSipGateway
|
||||
|
@ -1544,10 +1566,11 @@ function _mapStateToProps(state: IReduxState, ownProps: Partial<IProps>) {
|
|||
|
||||
return {
|
||||
_backgroundType: state['features/virtual-background'].backgroundType ?? '',
|
||||
_buttonsWithNotifyClick: buttonsWithNotifyClick,
|
||||
_buttonsWithNotifyClick: getButtonsWithNotifyClick(state),
|
||||
_chatOpen: state['features/chat'].isOpen,
|
||||
_clientWidth: clientWidth,
|
||||
_conference: conference,
|
||||
_customToolbarButtons: customToolbarButtons,
|
||||
_desktopSharingEnabled: JitsiMeetJS.isDesktopSharingEnabled(),
|
||||
_desktopSharingButtonDisabled: isDesktopShareButtonDisabled(state),
|
||||
_dialog: Boolean(state['features/base/dialog'].component),
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import React, { useCallback } from 'react';
|
||||
|
||||
import ContextMenuItem from '../../../base/ui/components/web/ContextMenuItem';
|
||||
|
||||
const CustomOptionButton = (
|
||||
{ icon: iconSrc, onClick, text }:
|
||||
{
|
||||
icon: string;
|
||||
onClick: (e?: React.MouseEvent<Element, MouseEvent> | undefined) => void;
|
||||
text: string;
|
||||
}
|
||||
) => {
|
||||
|
||||
const icon = useCallback(props => (<img
|
||||
src = { iconSrc }
|
||||
{ ...props } />), [ iconSrc ]);
|
||||
|
||||
return (
|
||||
<ContextMenuItem
|
||||
accessibilityLabel = { text }
|
||||
icon = { icon }
|
||||
onClick = { onClick }
|
||||
text = { text } />
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomOptionButton;
|
|
@ -26,6 +26,7 @@ import { isForceMuted } from '../../../participants-pane/functions';
|
|||
import { requestRemoteControl, stopController } from '../../../remote-control';
|
||||
import { showOverflowDrawer } from '../../../toolbox/functions.web';
|
||||
|
||||
import CustomOptionButton from './CustomOptionButton';
|
||||
// @ts-ignore
|
||||
import { REMOTE_CONTROL_MENU_STATES } from './RemoteControlButton';
|
||||
// @ts-ignore
|
||||
|
@ -144,7 +145,7 @@ const ParticipantContextMenu = ({
|
|||
isForceMuted(participant, MEDIA_TYPE.VIDEO, state));
|
||||
const _isAudioMuted = useSelector((state: IReduxState) => isParticipantAudioMuted(participant, state));
|
||||
const _overflowDrawer: boolean = useSelector(showOverflowDrawer);
|
||||
const { remoteVideoMenu = {}, disableRemoteMute, startSilent }
|
||||
const { remoteVideoMenu = {}, disableRemoteMute, startSilent, customParticipantMenuButtons }
|
||||
= useSelector((state: IReduxState) => state['features/base/config']);
|
||||
const { disableKick, disableGrantModerator, disablePrivateChat } = remoteVideoMenu;
|
||||
const { participantsVolume } = useSelector((state: IReduxState) => state['features/filmstrip']);
|
||||
|
@ -171,8 +172,8 @@ const ParticipantContextMenu = ({
|
|||
}
|
||||
, [ thumbnailMenu, _overflowDrawer, drawerParticipant, participant ]);
|
||||
|
||||
const buttons = [];
|
||||
const buttons2 = [];
|
||||
const buttons: JSX.Element[] = [];
|
||||
const buttons2: JSX.Element[] = [];
|
||||
|
||||
const showVolumeSlider = !startSilent
|
||||
&& !isIosMobileBrowser()
|
||||
|
@ -277,6 +278,23 @@ const ParticipantContextMenu = ({
|
|||
);
|
||||
}
|
||||
|
||||
if (customParticipantMenuButtons) {
|
||||
customParticipantMenuButtons.forEach(
|
||||
({ icon, id, text }) => {
|
||||
const onClick = useCallback(
|
||||
() => APP.API.notifyParticipantMenuButtonClicked(id, _getCurrentParticipantId()), []);
|
||||
|
||||
buttons2.push(
|
||||
<CustomOptionButton
|
||||
icon = { icon }
|
||||
key = { id }
|
||||
onClick = { onClick }
|
||||
text = { text } />
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const breakoutRoomsButtons: any = [];
|
||||
|
||||
if (!thumbnailMenu && _isModerator) {
|
||||
|
|
Loading…
Reference in New Issue