feat(toolbar-buttons): Add event for notify overwritten toolbar buttons

This commit is contained in:
Horatiu Muresan 2021-09-14 10:07:20 +03:00 committed by GitHub
parent aadbe59d00
commit 1add438a1f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 380 additions and 74 deletions

View File

@ -539,6 +539,43 @@ var config = {
// '__end'
// ],
// Toolbar buttons which have their click event exposed through the API on
// `toolbarButtonClicked` event instead of executing the normal click routine.
// buttonsWithNotifyClick: [
// 'camera',
// 'chat',
// 'closedcaptions',
// 'desktop',
// 'download',
// 'embedmeeting',
// 'etherpad',
// 'feedback',
// 'filmstrip',
// 'fullscreen',
// 'hangup',
// 'help',
// 'invite',
// 'livestreaming',
// 'microphone',
// 'mute-everyone',
// 'mute-video-everyone',
// 'participants-pane',
// 'profile',
// 'raisehand',
// 'recording',
// 'security',
// 'select-background',
// 'settings',
// 'shareaudio',
// 'sharedvideo',
// 'shortcuts',
// 'stats',
// 'tileview',
// 'toggle-camera',
// 'videoquality',
// '__end'
// ],
// List of pre meeting screens buttons to hide. The values must be one or more of the 5 allowed buttons:
// 'microphone', 'camera', 'select-background', 'invite', 'settings'
// hiddenPremeetingButtons: [],

View File

@ -1342,6 +1342,19 @@ class API {
});
}
/**
* Notify external application ( if API is enabled) that a toolbar button was clicked.
*
* @param {string} key - The key of the toolbar button.
* @returns {void}
*/
notifyToolbarButtonClicked(key: string) {
this._sendEvent({
name: 'toolbar-button-clicked',
key
});
}
/**
* Disposes the allocated resources.
*

View File

@ -112,7 +112,8 @@ const events = {
'dominant-speaker-changed': 'dominantSpeakerChanged',
'subject-change': 'subjectChange',
'suspend-detected': 'suspendDetected',
'tile-view-changed': 'tileViewChanged'
'tile-view-changed': 'tileViewChanged',
'toolbar-button-clicked': 'toolbarButtonClicked'
};
/**

View File

@ -19,6 +19,7 @@ export default [
'apiLogLevels',
'avgRtpStatsN',
'backgroundAlpha',
'buttonsWithNotifyClick',
/**
* The display name of the CallKit call representing the conference/meeting

View File

@ -23,6 +23,14 @@ export default class AbstractAudioMuteButton<P: Props, S: *>
* @returns {void}
*/
_handleClick() {
const { handleClick } = this.props;
if (handleClick) {
handleClick();
return;
}
this._setAudioMuted(!this._isAudioMuted());
}

View File

@ -20,6 +20,11 @@ export type Props = {
*/
disabledStyles: ?Styles,
/**
* External handler for click action.
*/
handleClick?: Function,
/**
* Whether to show the label or not.
*/

View File

@ -22,6 +22,14 @@ export default class AbstractVideoMuteButton<P : Props, S : *>
* @returns {void}
*/
_handleClick() {
const { handleClick } = this.props;
if (handleClick) {
handleClick();
return;
}
this._setVideoMuted(!this._isVideoMuted());
}

View File

@ -18,11 +18,6 @@ type Props = AbstractButtonProps & {
* Whether or not the chat feature is currently displayed.
*/
_chatOpen: boolean,
/**
* External handler for click action.
*/
handleClick: Function
};
/**
@ -61,7 +56,13 @@ class ChatButton extends AbstractButton<Props, *> {
* @returns {void}
*/
_handleClick() {
this.props.handleClick();
const { handleClick } = this.props;
if (handleClick) {
handleClick();
return;
}
}
/**

View File

@ -36,7 +36,13 @@ class EmbedMeetingButton extends AbstractButton<Props, *> {
* @returns {void}
*/
_handleClick() {
const { dispatch } = this.props;
const { dispatch, handleClick } = this.props;
if (handleClick) {
handleClick();
return;
}
sendAnalytics(createToolbarEvent('embed.meeting'));
dispatch(openDialog(EmbedMeetingDialog));

View File

@ -59,12 +59,20 @@ class SharedDocumentButton extends AbstractButton<Props, *> {
* @returns {void}
*/
_handleClick() {
const { _editing, dispatch, handleClick } = this.props;
if (handleClick) {
handleClick();
return;
}
sendAnalytics(createToolbarEvent(
'toggle.etherpad',
{
enable: !this.props._editing
enable: !_editing
}));
this.props.dispatch(toggleDocument());
dispatch(toggleDocument());
}
/**

View File

@ -40,7 +40,13 @@ class FeedbackButton extends AbstractButton<Props, *> {
* @returns {void}
*/
_handleClick() {
const { _conference, dispatch } = this.props;
const { _conference, dispatch, handleClick } = this.props;
if (handleClick) {
handleClick();
return;
}
sendAnalytics(createToolbarEvent('feedback'));
dispatch(openFeedbackDialog(_conference));

View File

@ -34,7 +34,13 @@ class InviteButton extends AbstractButton<Props, *> {
* @returns {void}
*/
_handleClick() {
const { dispatch } = this.props;
const { dispatch, handleClick } = this.props;
if (handleClick) {
handleClick();
return;
}
sendAnalytics(createToolbarEvent('invite'));
dispatch(beginAddPeople());

View File

@ -34,7 +34,13 @@ class KeyboardShortcutsButton extends AbstractButton<Props, *> {
* @returns {void}
*/
_handleClick() {
const { dispatch } = this.props;
const { dispatch, handleClick } = this.props;
if (handleClick) {
handleClick();
return;
}
sendAnalytics(createToolbarEvent('shortcuts'));
dispatch(openKeyboardShortcutsDialog());

View File

@ -36,7 +36,13 @@ class LocalRecording extends AbstractButton<Props, *> {
* @returns {void}
*/
_handleClick() {
const { dispatch } = this.props;
const { dispatch, handleClick } = this.props;
if (handleClick) {
handleClick();
return;
}
sendAnalytics(createToolbarEvent('local.recording'));
dispatch(openDialog(LocalRecordingInfoDialog));

View File

@ -14,11 +14,6 @@ type Props = AbstractButtonProps & {
* Whether or not the participants pane is open.
*/
_isOpen: boolean,
/**
* External handler for click action.
*/
handleClick: Function
};
/**
@ -37,7 +32,13 @@ class ParticipantsPaneButton extends AbstractButton<Props, *> {
* @returns {void}
*/
_handleClick() {
this.props.handleClick();
const { handleClick } = this.props;
if (handleClick) {
handleClick();
return;
}
}
/**

View File

@ -76,7 +76,13 @@ export default class AbstractLiveStreamButton<P: Props> extends AbstractButton<P
* @returns {void}
*/
async _handleClick() {
const { _isLiveStreamRunning, dispatch } = this.props;
const { _isLiveStreamRunning, dispatch, handleClick } = this.props;
if (handleClick) {
handleClick();
return;
}
const dialogShown = await dispatch(maybeShowPremiumFeatureDialog(FEATURES.RECORDING));

View File

@ -77,7 +77,13 @@ export default class AbstractRecordButton<P: Props> extends AbstractButton<P, *>
* @returns {void}
*/
async _handleClick() {
const { _isRecordingRunning, dispatch } = this.props;
const { _isRecordingRunning, dispatch, handleClick } = this.props;
if (handleClick) {
handleClick();
return;
}
sendAnalytics(createToolbarEvent(
'recording.button',

View File

@ -47,8 +47,16 @@ class ShareAudioButton extends AbstractButton<Props, *> {
* @returns {void}
*/
_handleClick() {
this.props.dispatch(startAudioScreenShareFlow());
this.props.dispatch(setOverflowMenuVisible(false));
const { dispatch, handleClick } = this.props;
if (handleClick) {
handleClick();
return;
}
dispatch(startAudioScreenShareFlow());
dispatch(setOverflowMenuVisible(false));
}
/**

View File

@ -48,8 +48,16 @@ class SecurityDialogButton extends AbstractButton<Props, *> {
* @returns {void}
*/
_handleClick() {
sendAnalytics(createToolbarEvent('toggle.security', { enable: !this.props._locked }));
this.props.dispatch(toggleSecurityDialog());
const { _locked, dispatch, handleClick } = this.props;
if (handleClick) {
handleClick();
return;
}
sendAnalytics(createToolbarEvent('toggle.security', { enable: !_locked }));
dispatch(toggleSecurityDialog());
}
/**

View File

@ -42,7 +42,15 @@ class SettingsButton extends AbstractButton<Props, *> {
_handleClick() {
const {
defaultTab = SETTINGS_TABS.DEVICES,
dispatch } = this.props;
dispatch,
handleClick
} = this.props;
if (handleClick) {
handleClick();
return;
}
sendAnalytics(createToolbarEvent('settings'));
dispatch(openSettingsDialog(defaultTab));

View File

@ -66,6 +66,14 @@ class SharedVideoButton extends AbstractButton<Props, *> {
* @returns {void}
*/
_handleClick() {
const { handleClick } = this.props;
if (handleClick) {
handleClick();
return;
}
this._doToggleSharedVideo();
}

View File

@ -41,7 +41,13 @@ class SpeakerStatsButton extends AbstractButton<Props, *> {
* @returns {void}
*/
_handleClick() {
const { _conference, dispatch } = this.props;
const { _conference, dispatch, handleClick } = this.props;
if (handleClick) {
handleClick();
return;
}
sendAnalytics(createToolbarEvent('speaker.stats'));
dispatch(openDialog(SpeakerStats, {

View File

@ -38,7 +38,13 @@ export class AbstractClosedCaptionButton
* @returns {void}
*/
async _handleClick() {
const { _requestingSubtitles, dispatch } = this.props;
const { _requestingSubtitles, dispatch, handleClick } = this.props;
if (handleClick) {
handleClick();
return;
}
sendAnalytics(createToolbarEvent('transcribing.ccButton',
{

View File

@ -32,8 +32,16 @@ class DownloadButton extends AbstractButton<Props, *> {
* @returns {void}
*/
_handleClick() {
const { _downloadAppsUrl, handleClick } = this.props;
if (handleClick) {
handleClick();
return;
}
sendAnalytics(createToolbarEvent('download.pressed'));
openURLInBrowser(this.props._downloadAppsUrl);
openURLInBrowser(_downloadAppsUrl);
}
}

View File

@ -33,8 +33,16 @@ class HelpButton extends AbstractButton<Props, *> {
* @returns {void}
*/
_handleClick() {
const { _userDocumentationURL, handleClick } = this.props;
if (handleClick) {
handleClick();
return;
}
sendAnalytics(createToolbarEvent('help.pressed'));
openURLInBrowser(this.props._userDocumentationURL);
openURLInBrowser(_userDocumentationURL);
}
}

View File

@ -39,7 +39,13 @@ class MuteEveryoneButton extends AbstractButton<Props, *> {
* @returns {void}
*/
_handleClick() {
const { dispatch, localParticipantId } = this.props;
const { dispatch, localParticipantId, handleClick } = this.props;
if (handleClick) {
handleClick();
return;
}
sendAnalytics(createToolbarEvent('mute.everyone.pressed'));
dispatch(openDialog(MuteEveryoneDialog, {

View File

@ -39,7 +39,13 @@ class MuteEveryonesVideoButton extends AbstractButton<Props, *> {
* @returns {void}
*/
_handleClick() {
const { dispatch, localParticipantId } = this.props;
const { dispatch, localParticipantId, handleClick } = this.props;
if (handleClick) {
handleClick();
return;
}
sendAnalytics(createToolbarEvent('mute.everyone.pressed'));
dispatch(openDialog(MuteEveryonesVideoDialog, {

View File

@ -15,6 +15,11 @@ import AudioMuteButton from '../AudioMuteButton';
type Props = {
/**
* External handler for click action.
*/
handleClick: Function,
/**
* Indicates whether audio permissions have been granted or denied.
*/
@ -62,6 +67,7 @@ class AudioSettingsButton extends Component<Props> {
super(props);
this._onEscClick = this._onEscClick.bind(this);
this._onClick = this._onClick.bind(this);
}
_onEscClick: (KeyboardEvent) => void;
@ -76,17 +82,36 @@ class AudioSettingsButton extends Component<Props> {
if (event.key === 'Escape' && this.props.isOpen) {
event.preventDefault();
event.stopPropagation();
this.props.onAudioOptionsClick();
this._onClick();
}
}
_onClick: () => void;
/**
* Click handler for the more actions entries.
*
* @returns {void}
*/
_onClick() {
const { handleClick, onAudioOptionsClick } = this.props;
if (handleClick) {
handleClick();
return;
}
onAudioOptionsClick();
}
/**
* Implements React's {@link Component#render}.
*
* @inheritdoc
*/
render() {
const { hasPermissions, isDisabled, onAudioOptionsClick, visible, isOpen, t } = this.props;
const { handleClick, hasPermissions, isDisabled, visible, isOpen, t } = this.props;
const settingsDisabled = !hasPermissions
|| isDisabled
|| !JitsiMeetJS.mediaDevices.isMultipleAudioInputSupported();
@ -102,12 +127,12 @@ class AudioSettingsButton extends Component<Props> {
iconDisabled = { settingsDisabled }
iconId = 'audio-settings-button'
iconTooltip = { t('toolbar.audioSettings') }
onIconClick = { onAudioOptionsClick }
onIconClick = { this._onClick }
onIconKeyDown = { this._onEscClick }>
<AudioMuteButton />
<AudioMuteButton handleClick = { handleClick } />
</ToolboxButtonWithIcon>
</AudioSettingsPopup>
) : <AudioMuteButton />;
) : <AudioMuteButton handleClick = { handleClick } />;
}
}

View File

@ -11,11 +11,6 @@ type Props = AbstractButtonProps & {
* Whether or not the app is currently in full screen.
*/
_fullScreen: boolean,
/**
* External handler for click action.
*/
handleClick: Function
};
/**
@ -73,7 +68,13 @@ class FullscreenButton extends AbstractButton<Props, *> {
* @returns {void}
*/
_handleClick() {
this.props.handleClick();
const { handleClick } = this.props;
if (handleClick) {
handleClick();
return;
}
}
/**

View File

@ -87,7 +87,13 @@ class ProfileButton extends AbstractButton<Props, *> {
* @returns {void}
*/
_handleClick() {
const { dispatch, _unclickable } = this.props;
const { dispatch, _unclickable, handleClick } = this.props;
if (handleClick) {
handleClick();
return;
}
if (!_unclickable) {
sendAnalytics(createToolbarEvent('profile'));

View File

@ -12,11 +12,6 @@ type Props = AbstractButtonProps & {
* Whether or not the local participant's hand is raised.
*/
_raisedHand: boolean,
/**
* External handler for click action.
*/
handleClick: Function
};
/**
@ -51,7 +46,13 @@ class RaiseHandButton extends AbstractButton<Props, *> {
* @returns {void}
*/
_handleClick() {
this.props.handleClick();
const { handleClick } = this.props;
if (handleClick) {
handleClick();
return;
}
}
/**

View File

@ -29,11 +29,6 @@ type Props = AbstractButtonProps & {
* The redux {@code dispatch} function.
*/
dispatch: Function,
/**
* External handler for click action.
*/
handleClick: Function
};
/**
@ -79,7 +74,13 @@ class ShareDesktopButton extends AbstractButton<Props, *> {
* @returns {void}
*/
_handleClick() {
this.props.handleClick();
const { handleClick } = this.props;
if (handleClick) {
handleClick();
return;
}
}
/**

View File

@ -41,7 +41,15 @@ class ToggleCameraButton extends AbstractButton<Props, any> {
* @returns {void}
*/
_handleClick() {
this.props.dispatch(toggleCamera());
const { dispatch, handleClick } = this.props;
if (handleClick) {
handleClick();
return;
}
dispatch(toggleCamera());
}
/**

View File

@ -103,6 +103,11 @@ type Props = {
*/
_backgroundType: String,
/**
* Toolbar buttons which have their click exposed through the API.
*/
_buttonsWithNotifyClick: Array<string>,
/**
* Whether or not the chat feature is currently displayed.
*/
@ -835,6 +840,24 @@ class Toolbox extends Component<Props, State> {
};
}
/**
* Overwrites click handlers for buttons in case click is exposed through the iframe API.
*
* @param {Object} buttons - The list of toolbar buttons.
* @returns {void}
*/
_overwriteButtonsClickHandlers(buttons) {
if (typeof APP === 'undefined' || !this.props._buttonsWithNotifyClick?.length) {
return;
}
Object.values(buttons).forEach((button: any) => {
if (this.props._buttonsWithNotifyClick.includes(button.key)) {
button.handleClick = () => APP.API.notifyToolbarButtonClicked(button.key);
}
});
}
/**
* Returns all buttons that need to be rendered.
*
@ -849,6 +872,8 @@ class Toolbox extends Component<Props, State> {
const buttons = this._getAllButtons();
this._overwriteButtonsClickHandlers(buttons);
const isHangupVisible = isToolbarButtonEnabled('hangup', _toolbarButtons);
const { order } = THRESHOLDS.find(({ width }) => _clientWidth > width)
|| THRESHOLDS[THRESHOLDS.length - 1];
@ -1313,7 +1338,9 @@ function _mapStateToProps(state, ownProps) {
let desktopSharingEnabled = JitsiMeetJS.isDesktopSharingEnabled();
const {
callStatsID,
enableFeaturesBasedOnToken
disableProfile,
enableFeaturesBasedOnToken,
buttonsWithNotifyClick
} = state['features/base/config'];
const {
fullScreen,
@ -1345,6 +1372,7 @@ function _mapStateToProps(state, ownProps) {
return {
_backgroundType: state['features/virtual-background'].backgroundType,
_buttonsWithNotifyClick: buttonsWithNotifyClick,
_chatOpen: state['features/chat'].isOpen,
_clientWidth: clientWidth,
_conference: conference,
@ -1353,7 +1381,7 @@ function _mapStateToProps(state, ownProps) {
_dialog: Boolean(state['features/base/dialog'].component),
_feedbackConfigured: Boolean(callStatsID),
_fullScreen: fullScreen,
_isProfileDisabled: Boolean(state['features/base/config'].disableProfile),
_isProfileDisabled: Boolean(disableProfile),
_isMobile: isMobileBrowser(),
_isVpaasMeeting: isVpaasMeeting(state),
_localParticipantID: localParticipant?.id,

View File

@ -16,6 +16,11 @@ import VideoMuteButton from '../VideoMuteButton';
type Props = {
/**
* External handler for click action.
*/
handleClick: Function,
/**
* Click handler for the small icon. Opens video options.
*/
@ -62,7 +67,7 @@ type Props = {
*/
class VideoSettingsButton extends Component<Props> {
/**
* Initializes a new {@code AudioSettingsButton} instance.
* Initializes a new {@code VideoSettingsButton} instance.
*
* @inheritdoc
*/
@ -70,6 +75,7 @@ class VideoSettingsButton extends Component<Props> {
super(props);
this._onEscClick = this._onEscClick.bind(this);
this._onClick = this._onClick.bind(this);
}
/**
@ -94,17 +100,36 @@ class VideoSettingsButton extends Component<Props> {
if (event.key === 'Escape' && this.props.isOpen) {
event.preventDefault();
event.stopPropagation();
this.props.onVideoOptionsClick();
this._onClick();
}
}
_onClick: () => void;
/**
* Click handler for the more actions entries.
*
* @returns {void}
*/
_onClick() {
const { handleClick, onVideoOptionsClick } = this.props;
if (handleClick) {
handleClick();
return;
}
onVideoOptionsClick();
}
/**
* Implements React's {@link Component#render}.
*
* @inheritdoc
*/
render() {
const { onVideoOptionsClick, t, visible, isOpen } = this.props;
const { handleClick, t, visible, isOpen } = this.props;
return visible ? (
<VideoSettingsPopup>
@ -117,12 +142,12 @@ class VideoSettingsButton extends Component<Props> {
iconDisabled = { this._isIconDisabled() }
iconId = 'video-settings-button'
iconTooltip = { t('toolbar.videoSettings') }
onIconClick = { onVideoOptionsClick }
onIconClick = { this._onClick }
onIconKeyDown = { this._onEscClick }>
<VideoMuteButton />
<VideoMuteButton handleClick = { handleClick } />
</ToolboxButtonWithIcon>
</VideoSettingsPopup>
) : <VideoMuteButton />;
) : <VideoMuteButton handleClick = { handleClick } />;
}
}

View File

@ -51,7 +51,14 @@ class TileViewButton<P: Props> extends AbstractButton<P, *> {
* @returns {void}
*/
_handleClick() {
const { _tileViewEnabled, dispatch } = this.props;
const { _tileViewEnabled, dispatch, handleClick } = this.props;
if (handleClick) {
handleClick();
return;
}
const value = !_tileViewEnabled;
sendAnalytics(createToolbarEvent(

View File

@ -42,11 +42,6 @@ type Props = AbstractButtonProps & {
*/
_videoQuality: number,
/**
* Callback to invoke when {@link VideoQualityButton} is clicked.
*/
handleClick: Function,
/**
* Invoked to obtain translated strings.
*/
@ -97,7 +92,13 @@ class VideoQualityButton extends AbstractButton<Props, *> {
* @returns {void}
*/
_handleClick() {
this.props.handleClick();
const { handleClick } = this.props;
if (handleClick) {
handleClick();
return;
}
}
}

View File

@ -43,7 +43,13 @@ class VideoBackgroundButton extends AbstractButton<Props, *> {
* @returns {void}
*/
_handleClick() {
const { dispatch } = this.props;
const { dispatch, handleClick } = this.props;
if (handleClick) {
handleClick();
return;
}
dispatch(openDialog(VirtualBackgroundDialog));
}