mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-05-14 17:07:48 +00:00
[RN] Add remote video menu
This commit is contained in:
committed by
Zoltan Bettenbuk
parent
d4c0840659
commit
6b68fba220
105
react/features/remote-video-menu/components/web/KickButton.js
Normal file
105
react/features/remote-video-menu/components/web/KickButton.js
Normal file
@@ -0,0 +1,105 @@
|
||||
/* @flow */
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import {
|
||||
createRemoteVideoMenuButtonEvent,
|
||||
sendAnalytics
|
||||
} from '../../../analytics';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { kickParticipant } from '../../../base/participants';
|
||||
|
||||
import RemoteVideoMenuButton from './RemoteVideoMenuButton';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} state of {@link KickButton}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Invoked to signal the participant with the passed in participantID
|
||||
* should be removed from the conference.
|
||||
*/
|
||||
dispatch: Dispatch<*>,
|
||||
|
||||
/**
|
||||
* Callback to invoke when {@code KickButton} is clicked.
|
||||
*/
|
||||
onClick: Function,
|
||||
|
||||
/**
|
||||
* The ID of the participant linked to the onClick callback.
|
||||
*/
|
||||
participantID: string,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function,
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a React {@link Component} which displays a button for kicking out
|
||||
* a participant from the conference.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
class KickButton extends Component<Props> {
|
||||
/**
|
||||
* Initializes a new {@code KickButton} instance.
|
||||
*
|
||||
* @param {Object} props - The read-only React Component props with which
|
||||
* the new instance is to be initialized.
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onClick = this._onClick.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { participantID, t } = this.props;
|
||||
|
||||
return (
|
||||
<RemoteVideoMenuButton
|
||||
buttonText = { t('videothumbnail.kick') }
|
||||
iconClass = 'icon-kick'
|
||||
id = { `ejectlink_${participantID}` }
|
||||
onClick = { this._onClick } />
|
||||
);
|
||||
}
|
||||
|
||||
_onClick: () => void;
|
||||
|
||||
/**
|
||||
* Remove the participant with associated participantID from the conference.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onClick() {
|
||||
const { dispatch, onClick, participantID } = this.props;
|
||||
|
||||
sendAnalytics(createRemoteVideoMenuButtonEvent(
|
||||
'kick.button',
|
||||
{
|
||||
'participant_id': participantID
|
||||
}));
|
||||
|
||||
dispatch(kickParticipant(participantID));
|
||||
|
||||
if (onClick) {
|
||||
onClick();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(connect()(KickButton));
|
||||
120
react/features/remote-video-menu/components/web/MuteButton.js
Normal file
120
react/features/remote-video-menu/components/web/MuteButton.js
Normal file
@@ -0,0 +1,120 @@
|
||||
/* @flow */
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import {
|
||||
createRemoteVideoMenuButtonEvent,
|
||||
sendAnalytics
|
||||
} from '../../../analytics';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { openDialog } from '../../../base/dialog';
|
||||
|
||||
import RemoteVideoMenuButton from './RemoteVideoMenuButton';
|
||||
import MuteRemoteParticipantDialog from './MuteRemoteParticipantDialog';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link MuteButton}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Invoked to send a request for muting the participant with the passed
|
||||
* in participantID.
|
||||
*/
|
||||
dispatch: Dispatch<*>,
|
||||
|
||||
/**
|
||||
* Whether or not the participant is currently audio muted.
|
||||
*/
|
||||
isAudioMuted: Function,
|
||||
|
||||
/**
|
||||
* Callback to invoke when {@code MuteButton} is clicked.
|
||||
*/
|
||||
onClick: Function,
|
||||
|
||||
/**
|
||||
* The ID of the participant linked to the onClick callback.
|
||||
*/
|
||||
participantID: string,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a React {@link Component} which displays a button for audio muting
|
||||
* a participant in the conference.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
class MuteButton extends Component<Props> {
|
||||
/**
|
||||
* Initializes a new {@code MuteButton} instance.
|
||||
*
|
||||
* @param {Object} props - The read-only React Component props with which
|
||||
* the new instance is to be initialized.
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onClick = this._onClick.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { isAudioMuted, participantID, t } = this.props;
|
||||
const muteConfig = isAudioMuted ? {
|
||||
translationKey: 'videothumbnail.muted',
|
||||
muteClassName: 'mutelink disabled'
|
||||
} : {
|
||||
translationKey: 'videothumbnail.domute',
|
||||
muteClassName: 'mutelink'
|
||||
};
|
||||
|
||||
return (
|
||||
<RemoteVideoMenuButton
|
||||
buttonText = { t(muteConfig.translationKey) }
|
||||
displayClass = { muteConfig.muteClassName }
|
||||
iconClass = 'icon-mic-disabled'
|
||||
id = { `mutelink_${participantID}` }
|
||||
onClick = { this._onClick } />
|
||||
);
|
||||
}
|
||||
|
||||
_onClick: () => void;
|
||||
|
||||
/**
|
||||
* Dispatches a request to mute the participant with the passed in
|
||||
* participantID.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onClick() {
|
||||
const { dispatch, onClick, participantID } = this.props;
|
||||
|
||||
sendAnalytics(createRemoteVideoMenuButtonEvent(
|
||||
'mute.button',
|
||||
{
|
||||
'participant_id': participantID
|
||||
}));
|
||||
|
||||
dispatch(openDialog(MuteRemoteParticipantDialog, { participantID }));
|
||||
|
||||
if (onClick) {
|
||||
onClick();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(connect()(MuteButton));
|
||||
@@ -0,0 +1,116 @@
|
||||
/* @flow */
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { Dialog } from '../../../base/dialog';
|
||||
import { translate } from '../../../base/i18n';
|
||||
|
||||
import {
|
||||
createRemoteMuteConfirmedEvent,
|
||||
sendAnalytics
|
||||
} from '../../../analytics';
|
||||
import { muteRemoteParticipant } from '../../../base/participants';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of
|
||||
* {@link MuteRemoteParticipantDialog}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Invoked to send a request for muting the participant with the passed
|
||||
* in participantID.
|
||||
*/
|
||||
dispatch: Dispatch<*>,
|
||||
|
||||
/**
|
||||
* The ID of the participant linked to the onClick callback.
|
||||
*/
|
||||
participantID: string,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* A React Component with the contents for a dialog that asks for confirmation
|
||||
* from the user before muting a remote participant.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
class MuteRemoteParticipantDialog extends Component<Props> {
|
||||
/**
|
||||
* Initializes a new {@code MuteRemoteParticipantDialog} instance.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onSubmit = this._onSubmit.bind(this);
|
||||
this._renderContent = this._renderContent.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<Dialog
|
||||
okTitleKey = 'dialog.muteParticipantButton'
|
||||
onSubmit = { this._onSubmit }
|
||||
titleKey = 'dialog.muteParticipantTitle'
|
||||
width = 'small'>
|
||||
{ this._renderContent() }
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
_onSubmit: () => void;
|
||||
|
||||
/**
|
||||
* Handles the submit button action.
|
||||
*
|
||||
* @private
|
||||
* @returns {boolean} - True (to note that the modal should be closed).
|
||||
*/
|
||||
_onSubmit() {
|
||||
const { dispatch, participantID } = this.props;
|
||||
|
||||
sendAnalytics(createRemoteMuteConfirmedEvent(participantID));
|
||||
|
||||
dispatch(muteRemoteParticipant(participantID));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_renderContent: () => React$Element<*>;
|
||||
|
||||
/**
|
||||
* Renders the content of the dialog.
|
||||
*
|
||||
* @private
|
||||
* @returns {Component} The React {@code Component} which is the view of the
|
||||
* dialog content.
|
||||
*/
|
||||
_renderContent() {
|
||||
const { t } = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{ t('dialog.muteParticipantBody') }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default translate(connect()(MuteRemoteParticipantDialog));
|
||||
@@ -0,0 +1,146 @@
|
||||
/* @flow */
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import {
|
||||
createRemoteVideoMenuButtonEvent,
|
||||
sendAnalytics
|
||||
} from '../../../analytics';
|
||||
import { translate } from '../../../base/i18n';
|
||||
|
||||
import RemoteVideoMenuButton from './RemoteVideoMenuButton';
|
||||
|
||||
// TODO: Move these enums into the store after further reactification of the
|
||||
// non-react RemoteVideo component.
|
||||
export const REMOTE_CONTROL_MENU_STATES = {
|
||||
NOT_SUPPORTED: 0,
|
||||
NOT_STARTED: 1,
|
||||
REQUESTING: 2,
|
||||
STARTED: 3
|
||||
};
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link RemoteControlButton}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The callback to invoke when the component is clicked.
|
||||
*/
|
||||
onClick: Function,
|
||||
|
||||
/**
|
||||
* The ID of the participant linked to the onClick callback.
|
||||
*/
|
||||
participantID: string,
|
||||
|
||||
/**
|
||||
* The current status of remote control. Should be a number listed in the
|
||||
* enum REMOTE_CONTROL_MENU_STATES.
|
||||
*/
|
||||
remoteControlState: number,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a React {@link Component} which displays a button showing the
|
||||
* current state of remote control for a participant and can start or stop a
|
||||
* remote control session.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
class RemoteControlButton extends Component<Props> {
|
||||
/**
|
||||
* Initializes a new {@code RemoteControlButton} instance.
|
||||
*
|
||||
* @param {Object} props - The read-only React Component props with which
|
||||
* the new instance is to be initialized.
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onClick = this._onClick.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {null|ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const {
|
||||
participantID,
|
||||
remoteControlState,
|
||||
t
|
||||
} = this.props;
|
||||
|
||||
let className, icon;
|
||||
|
||||
switch (remoteControlState) {
|
||||
case REMOTE_CONTROL_MENU_STATES.NOT_STARTED:
|
||||
icon = 'icon-play';
|
||||
break;
|
||||
case REMOTE_CONTROL_MENU_STATES.REQUESTING:
|
||||
className = ' disabled';
|
||||
icon = 'icon-play';
|
||||
break;
|
||||
case REMOTE_CONTROL_MENU_STATES.STARTED:
|
||||
icon = 'icon-stop';
|
||||
break;
|
||||
case REMOTE_CONTROL_MENU_STATES.NOT_SUPPORTED:
|
||||
|
||||
// Intentionally fall through.
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<RemoteVideoMenuButton
|
||||
buttonText = { t('videothumbnail.remoteControl') }
|
||||
displayClass = { className }
|
||||
iconClass = { icon }
|
||||
id = { `remoteControl_${participantID}` }
|
||||
onClick = { this._onClick } />
|
||||
);
|
||||
}
|
||||
|
||||
_onClick: () => void;
|
||||
|
||||
/**
|
||||
* Sends analytics event for pressing the button and executes the passed
|
||||
* onClick handler.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onClick() {
|
||||
const { onClick, participantID, remoteControlState } = this.props;
|
||||
|
||||
// TODO: What do we do in case the state is e.g. "requesting"?
|
||||
if (remoteControlState === REMOTE_CONTROL_MENU_STATES.STARTED
|
||||
|| remoteControlState === REMOTE_CONTROL_MENU_STATES.NOT_STARTED) {
|
||||
|
||||
const enable
|
||||
= remoteControlState === REMOTE_CONTROL_MENU_STATES.NOT_STARTED;
|
||||
|
||||
sendAnalytics(createRemoteVideoMenuButtonEvent(
|
||||
'remote.control.button',
|
||||
{
|
||||
enable,
|
||||
'participant_id': participantID
|
||||
}));
|
||||
}
|
||||
|
||||
if (onClick) {
|
||||
onClick();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(RemoteControlButton);
|
||||
@@ -0,0 +1,44 @@
|
||||
/* @flow */
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link RemoteVideoMenu}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The components to place as the body of the {@code RemoteVideoMenu}.
|
||||
*/
|
||||
children: React$Node,
|
||||
|
||||
/**
|
||||
* The id attribute to be added to the component's DOM for retrieval when
|
||||
* querying the DOM. Not used directly by the component.
|
||||
*/
|
||||
id: string
|
||||
};
|
||||
|
||||
/**
|
||||
* React {@code Component} responsible for displaying other components as a menu
|
||||
* for manipulating remote participant state.
|
||||
*
|
||||
* @extends {Component}
|
||||
*/
|
||||
export default class RemoteVideoMenu extends Component<Props> {
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<ul
|
||||
className = 'popupmenu'
|
||||
id = { this.props.id }>
|
||||
{ this.props.children }
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/* @flow */
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of
|
||||
* {@link RemoteVideoMenuButton}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Text to display within the component that describes the onClick action.
|
||||
*/
|
||||
buttonText: string,
|
||||
|
||||
/**
|
||||
* Additional CSS classes to add to the component.
|
||||
*/
|
||||
displayClass?: string,
|
||||
|
||||
/**
|
||||
* The CSS classes for the icon that will display within the component.
|
||||
*/
|
||||
iconClass: string,
|
||||
|
||||
/**
|
||||
* The id attribute to be added to the component's DOM for retrieval when
|
||||
* querying the DOM. Not used directly by the component.
|
||||
*/
|
||||
id: string,
|
||||
|
||||
/**
|
||||
* Callback to invoke when the component is clicked.
|
||||
*/
|
||||
onClick: Function,
|
||||
};
|
||||
|
||||
/**
|
||||
* React {@code Component} for displaying an action in {@code RemoteVideoMenu}.
|
||||
*
|
||||
* @extends {Component}
|
||||
*/
|
||||
export default class RemoteVideoMenuButton extends Component<Props> {
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const {
|
||||
buttonText,
|
||||
displayClass,
|
||||
iconClass,
|
||||
id,
|
||||
onClick
|
||||
} = this.props;
|
||||
|
||||
const linkClassName = `popupmenu__link ${displayClass || ''}`;
|
||||
|
||||
return (
|
||||
<li className = 'popupmenu__item'>
|
||||
<a
|
||||
className = { linkClassName }
|
||||
id = { id }
|
||||
onClick = { onClick }>
|
||||
<span className = 'popupmenu__icon'>
|
||||
<i className = { iconClass } />
|
||||
</span>
|
||||
<span className = 'popupmenu__text'>
|
||||
{ buttonText }
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { Popover } from '../../../base/popover';
|
||||
|
||||
import {
|
||||
MuteButton,
|
||||
KickButton,
|
||||
RemoteControlButton,
|
||||
RemoteVideoMenu,
|
||||
VolumeSlider
|
||||
} from './';
|
||||
|
||||
declare var $: Object;
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of
|
||||
* {@link RemoteVideoMenuTriggerButton}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* A value between 0 and 1 indicating the volume of the participant's
|
||||
* audio element.
|
||||
*/
|
||||
initialVolumeValue: number,
|
||||
|
||||
/**
|
||||
* Whether or not the participant is currently muted.
|
||||
*/
|
||||
isAudioMuted: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the participant is a conference moderator.
|
||||
*/
|
||||
isModerator: boolean,
|
||||
|
||||
/**
|
||||
* Callback to invoke when the popover has been displayed.
|
||||
*/
|
||||
onMenuDisplay: Function,
|
||||
|
||||
/**
|
||||
* Callback to invoke choosing to start a remote control session with
|
||||
* the participant.
|
||||
*/
|
||||
onRemoteControlToggle: Function,
|
||||
|
||||
/**
|
||||
* Callback to invoke when changing the level of the participant's
|
||||
* audio element.
|
||||
*/
|
||||
onVolumeChange: Function,
|
||||
|
||||
/**
|
||||
* The position relative to the trigger the remote menu should display
|
||||
* from. Valid values are those supported by AtlasKit
|
||||
* {@code InlineDialog}.
|
||||
*/
|
||||
menuPosition: string,
|
||||
|
||||
/**
|
||||
* The ID for the participant on which the remote video menu will act.
|
||||
*/
|
||||
participantID: string,
|
||||
|
||||
/**
|
||||
* The current state of the participant's remote control session.
|
||||
*/
|
||||
remoteControlState: number
|
||||
};
|
||||
|
||||
/**
|
||||
* React {@code Component} for displaying an icon associated with opening the
|
||||
* the {@code RemoteVideoMenu}.
|
||||
*
|
||||
* @extends {Component}
|
||||
*/
|
||||
class RemoteVideoMenuTriggerButton extends Component<Props> {
|
||||
/**
|
||||
* The internal reference to topmost DOM/HTML element backing the React
|
||||
* {@code Component}. Accessed directly for associating an element as
|
||||
* the trigger for a popover.
|
||||
*
|
||||
* @private
|
||||
* @type {HTMLDivElement}
|
||||
*/
|
||||
_rootElement = null;
|
||||
|
||||
/**
|
||||
* Initializes a new {#@code RemoteVideoMenuTriggerButton} instance.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props: Object) {
|
||||
super(props);
|
||||
|
||||
// Bind event handler so it is only bound once for every instance.
|
||||
this._onShowRemoteMenu = this._onShowRemoteMenu.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const content = this._renderRemoteVideoMenu();
|
||||
|
||||
if (!content) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover
|
||||
content = { content }
|
||||
onPopoverOpen = { this._onShowRemoteMenu }
|
||||
position = { this.props.menuPosition }>
|
||||
<span
|
||||
className = 'popover-trigger remote-video-menu-trigger'>
|
||||
<i
|
||||
className = 'icon-thumb-menu'
|
||||
title = 'Remote user controls' />
|
||||
</span>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
_onShowRemoteMenu: () => void;
|
||||
|
||||
/**
|
||||
* Opens the {@code RemoteVideoMenu}.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onShowRemoteMenu() {
|
||||
this.props.onMenuDisplay();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code RemoteVideoMenu} with buttons for interacting with
|
||||
* the remote participant.
|
||||
*
|
||||
* @private
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderRemoteVideoMenu() {
|
||||
const {
|
||||
initialVolumeValue,
|
||||
isAudioMuted,
|
||||
isModerator,
|
||||
onRemoteControlToggle,
|
||||
onVolumeChange,
|
||||
remoteControlState,
|
||||
participantID
|
||||
} = this.props;
|
||||
|
||||
const buttons = [];
|
||||
|
||||
if (isModerator) {
|
||||
buttons.push(
|
||||
<MuteButton
|
||||
isAudioMuted = { isAudioMuted }
|
||||
key = 'mute'
|
||||
participantID = { participantID } />
|
||||
);
|
||||
buttons.push(
|
||||
<KickButton
|
||||
key = 'kick'
|
||||
participantID = { participantID } />
|
||||
);
|
||||
}
|
||||
|
||||
if (remoteControlState) {
|
||||
buttons.push(
|
||||
<RemoteControlButton
|
||||
key = 'remote-control'
|
||||
onClick = { onRemoteControlToggle }
|
||||
participantID = { participantID }
|
||||
remoteControlState = { remoteControlState } />
|
||||
);
|
||||
}
|
||||
|
||||
if (onVolumeChange) {
|
||||
buttons.push(
|
||||
<VolumeSlider
|
||||
initialValue = { initialVolumeValue }
|
||||
key = 'volume-slider'
|
||||
onChange = { onVolumeChange } />
|
||||
);
|
||||
}
|
||||
|
||||
if (buttons.length > 0) {
|
||||
return (
|
||||
<RemoteVideoMenu id = { participantID }>
|
||||
{ buttons }
|
||||
</RemoteVideoMenu>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default RemoteVideoMenuTriggerButton;
|
||||
111
react/features/remote-video-menu/components/web/VolumeSlider.js
Normal file
111
react/features/remote-video-menu/components/web/VolumeSlider.js
Normal file
@@ -0,0 +1,111 @@
|
||||
/* @flow */
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
/**
|
||||
* Used to modify initialValue, which is expected to be a decimal value between
|
||||
* 0 and 1, and converts it to a number representable by an input slider, which
|
||||
* recognizes whole numbers.
|
||||
*/
|
||||
const VOLUME_SLIDER_SCALE = 100;
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link VolumeSlider}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The value of the audio slider should display at when the component first
|
||||
* mounts. Changes will be stored in state. The value should be a number
|
||||
* between 0 and 1.
|
||||
*/
|
||||
initialValue: number,
|
||||
|
||||
/**
|
||||
* The callback to invoke when the audio slider value changes.
|
||||
*/
|
||||
onChange: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} state of {@link VolumeSlider}.
|
||||
*/
|
||||
type State = {
|
||||
|
||||
/**
|
||||
* The volume of the participant's audio element. The value will
|
||||
* be represented by a slider.
|
||||
*/
|
||||
volumeLevel: number
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a React {@link Component} which displays an input slider for
|
||||
* adjusting the local volume of a remote participant.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
class VolumeSlider extends Component<Props, State> {
|
||||
/**
|
||||
* Initializes a new {@code VolumeSlider} instance.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
volumeLevel: (props.initialValue || 0) * VOLUME_SLIDER_SCALE
|
||||
};
|
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onVolumeChange = this._onVolumeChange.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<li className = 'popupmenu__item'>
|
||||
<div className = 'popupmenu__contents'>
|
||||
<span className = 'popupmenu__icon'>
|
||||
<i className = 'icon-volume' />
|
||||
</span>
|
||||
<div className = 'popupmenu__slider_container'>
|
||||
<input
|
||||
className = 'popupmenu__slider'
|
||||
max = { VOLUME_SLIDER_SCALE }
|
||||
min = { 0 }
|
||||
onChange = { this._onVolumeChange }
|
||||
type = 'range'
|
||||
value = { this.state.volumeLevel } />
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
_onVolumeChange: (Object) => void;
|
||||
|
||||
/**
|
||||
* Sets the internal state of the volume level for the volume slider.
|
||||
* Invokes the prop onVolumeChange to notify of volume changes.
|
||||
*
|
||||
* @param {Object} event - DOM Event for slider change.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onVolumeChange(event) {
|
||||
const volumeLevel = event.currentTarget.value;
|
||||
|
||||
this.props.onChange(volumeLevel / VOLUME_SLIDER_SCALE);
|
||||
this.setState({ volumeLevel });
|
||||
}
|
||||
}
|
||||
|
||||
export default VolumeSlider;
|
||||
13
react/features/remote-video-menu/components/web/index.js
Normal file
13
react/features/remote-video-menu/components/web/index.js
Normal file
@@ -0,0 +1,13 @@
|
||||
// @flow
|
||||
|
||||
export { default as KickButton } from './KickButton';
|
||||
export { default as MuteButton } from './MuteButton';
|
||||
export {
|
||||
REMOTE_CONTROL_MENU_STATES,
|
||||
default as RemoteControlButton
|
||||
} from './RemoteControlButton';
|
||||
export { default as RemoteVideoMenu } from './RemoteVideoMenu';
|
||||
export {
|
||||
default as RemoteVideoMenuTriggerButton
|
||||
} from './RemoteVideoMenuTriggerButton';
|
||||
export { default as VolumeSlider } from './VolumeSlider';
|
||||
Reference in New Issue
Block a user