From 41f11e5adb7a012f80ea224cefd06a96d4638622 Mon Sep 17 00:00:00 2001 From: robertpin Date: Tue, 7 Dec 2021 10:24:00 +0200 Subject: [PATCH] feat(self-view) Added ability to hide self view Added config option disableSelfView. This disables it on web and native Added button on local video menu and toggle in settings on web to change the setting --- config.js | 3 + lang/main.json | 2 + react/features/base/config/configWhitelist.js | 1 + react/features/base/config/middleware.js | 6 + react/features/base/settings/reducer.js | 1 + react/features/filmstrip/actions.web.js | 8 +- .../filmstrip/components/native/Filmstrip.js | 18 ++- .../filmstrip/components/native/TileView.js | 14 +- .../filmstrip/components/web/Filmstrip.js | 35 +++-- .../components/web/ThumbnailWrapper.js | 18 ++- react/features/filmstrip/functions.any.js | 15 +++ react/features/filmstrip/subscriber.web.js | 11 +- react/features/notifications/middleware.js | 18 ++- react/features/settings/actions.js | 17 +++ .../settings/components/web/ProfileTab.js | 27 ++++ react/features/settings/functions.js | 2 + react/features/video-layout/functions.js | 6 +- .../components/web/HideSelfViewVideoButton.js | 120 ++++++++++++++++++ .../web/LocalVideoMenuTriggerButton.js | 2 + 19 files changed, 297 insertions(+), 27 deletions(-) create mode 100644 react/features/video-menu/components/web/HideSelfViewVideoButton.js diff --git a/config.js b/config.js index 2923079e8..eb3063691 100644 --- a/config.js +++ b/config.js @@ -83,6 +83,9 @@ var config = { // Disables polls feature. // disablePolls: false, + // Disables self-view tile. (hides it from tile view and from filmstrip) + // disableSelfView: false, + // Disables ICE/UDP by filtering out local and remote UDP candidates in // signalling. // webrtcIceUdpDisable: false, diff --git a/lang/main.json b/lang/main.json index 236326c8c..8f79221fb 100644 --- a/lang/main.json +++ b/lang/main.json @@ -608,6 +608,7 @@ "raisedHands": "{{participantName}} and {{raisedHands}} more people", "screenShareNoAudio": " Share audio box was not checked in the window selection screen.", "screenShareNoAudioTitle": "Couldn't share system audio!", + "selfViewTitle": "You can always un-hide the self-view from settings", "somebody": "Somebody", "startSilentTitle": "You joined with no audio output!", "startSilentDescription": "Rejoin the meeting to enable audio", @@ -1124,6 +1125,7 @@ "domuteVideoOfOthers": "Disable camera of everyone else", "flip": "Flip", "grantModerator": "Grant Moderator Rights", + "hideSelfView": "Hide self view", "kick": "Kick out", "moderator": "Moderator", "mute": "Participant is muted", diff --git a/react/features/base/config/configWhitelist.js b/react/features/base/config/configWhitelist.js index f6b530964..d5604c127 100644 --- a/react/features/base/config/configWhitelist.js +++ b/react/features/base/config/configWhitelist.js @@ -113,6 +113,7 @@ export default [ 'disableRemoteMute', 'disableResponsiveTiles', 'disableRtx', + 'disableSelfView', 'disableScreensharingVirtualBackground', 'disableShortcuts', 'disableShowMoreStats', diff --git a/react/features/base/config/middleware.js b/react/features/base/config/middleware.js index a512def0c..fecd1e80f 100644 --- a/react/features/base/config/middleware.js +++ b/react/features/base/config/middleware.js @@ -125,6 +125,12 @@ function _setConfig({ dispatch, getState }, next, action) { })); } + if (action.config.disableSelfView) { + dispatch(updateSettings({ + disableSelfView: true + })); + } + dispatch(updateConfig(config)); // FIXME On Web we rely on the global 'config' variable which gets altered diff --git a/react/features/base/settings/reducer.js b/react/features/base/settings/reducer.js index ad286385a..3ba7b7d44 100644 --- a/react/features/base/settings/reducer.js +++ b/react/features/base/settings/reducer.js @@ -21,6 +21,7 @@ const DEFAULT_STATE = { disableCallIntegration: undefined, disableCrashReporting: undefined, disableP2P: undefined, + disableSelfView: false, displayName: undefined, email: undefined, localFlipX: true, diff --git a/react/features/filmstrip/actions.web.js b/react/features/filmstrip/actions.web.js index d153f9a40..a328f3825 100644 --- a/react/features/filmstrip/actions.web.js +++ b/react/features/filmstrip/actions.web.js @@ -23,6 +23,7 @@ import { calculateThumbnailSizeForTileView, calculateThumbnailSizeForVerticalView } from './functions'; +import { getDisableSelfView } from './functions.any'; export * from './actions.any'; @@ -78,6 +79,7 @@ export function setVerticalViewDimensions() { return (dispatch: Dispatch, getState: Function) => { const state = getState(); const { clientHeight = 0, clientWidth = 0 } = state['features/base/responsive-ui']; + const disableSelfView = getDisableSelfView(state); const thumbnails = calculateThumbnailSizeForVerticalView(clientWidth); dispatch({ @@ -87,7 +89,8 @@ export function setVerticalViewDimensions() { remoteVideosContainer: { width: thumbnails?.local?.width + TILE_HORIZONTAL_MARGIN + STAGE_VIEW_THUMBNAIL_HORIZONTAL_BORDER + SCROLL_SIZE, - height: clientHeight - thumbnails?.local?.height - VERTICAL_FILMSTRIP_VERTICAL_MARGIN + height: clientHeight - (disableSelfView ? 0 : thumbnails?.local?.height) + - VERTICAL_FILMSTRIP_VERTICAL_MARGIN } } @@ -104,6 +107,7 @@ export function setHorizontalViewDimensions() { return (dispatch: Dispatch, getState: Function) => { const state = getState(); const { clientHeight = 0, clientWidth = 0 } = state['features/base/responsive-ui']; + const disableSelfView = getDisableSelfView(state); const thumbnails = calculateThumbnailSizeForHorizontalView(clientHeight); dispatch({ @@ -111,7 +115,7 @@ export function setHorizontalViewDimensions() { dimensions: { ...thumbnails, remoteVideosContainer: { - width: clientWidth - thumbnails?.local?.width - HORIZONTAL_FILMSTRIP_MARGIN, + width: clientWidth - (disableSelfView ? 0 : thumbnails?.local?.width) - HORIZONTAL_FILMSTRIP_MARGIN, height: thumbnails?.local?.height + TILE_VERTICAL_MARGIN + STAGE_VIEW_THUMBNAIL_VERTICAL_BORDER + SCROLL_SIZE } diff --git a/react/features/filmstrip/components/native/Filmstrip.js b/react/features/filmstrip/components/native/Filmstrip.js index 3f02a549b..d28b53cd6 100644 --- a/react/features/filmstrip/components/native/Filmstrip.js +++ b/react/features/filmstrip/components/native/Filmstrip.js @@ -9,6 +9,7 @@ import { connect } from '../../../base/redux'; import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui/constants'; import { setVisibleRemoteParticipants } from '../../actions'; import { isFilmstripVisible, shouldRemoteVideosBeVisible } from '../../functions'; +import { getDisableSelfView } from '../../functions.any'; import LocalThumbnail from './LocalThumbnail'; import Thumbnail from './Thumbnail'; @@ -31,6 +32,11 @@ type Props = { _clientHeight: number, + /** + * Whether or not to hide the self view. + */ + _disableSelfView: boolean, + _localParticipantId: string, /** @@ -215,7 +221,7 @@ class Filmstrip extends PureComponent { * @returns {ReactElement} */ render() { - const { _aspectRatio, _localParticipantId, _participants, _visible } = this.props; + const { _aspectRatio, _localParticipantId, _participants, _visible, _disableSelfView } = this.props; if (!_visible) { return null; @@ -229,13 +235,15 @@ class Filmstrip extends PureComponent { ? width / (thumbnailWidth + (2 * margin)) : height / (thumbnailHeight + (2 * margin)) ); - const participants = this._separateLocalThumbnail ? _participants : [ _localParticipantId, ..._participants ]; + const participants = this._separateLocalThumbnail || _disableSelfView + ? _participants : [ _localParticipantId, ..._participants ]; return ( { this._separateLocalThumbnail && !isNarrowAspectRatio + && !_disableSelfView && } { viewabilityConfig = { this._viewabilityConfig } windowSize = { 2 } /> { - this._separateLocalThumbnail && isNarrowAspectRatio + this._separateLocalThumbnail + && isNarrowAspectRatio + && !_disableSelfView && } @@ -271,6 +281,7 @@ class Filmstrip extends PureComponent { */ function _mapStateToProps(state) { const { enabled, remoteParticipants } = state['features/filmstrip']; + const disableSelfView = getDisableSelfView(state); const showRemoteVideos = shouldRemoteVideosBeVisible(state); const responsiveUI = state['features/base/responsive-ui']; @@ -278,6 +289,7 @@ function _mapStateToProps(state) { _aspectRatio: state['features/base/responsive-ui'].aspectRatio, _clientHeight: responsiveUI.clientHeight, _clientWidth: responsiveUI.clientWidth, + _disableSelfView: disableSelfView, _localParticipantId: getLocalParticipant(state)?.id, _participants: showRemoteVideos ? remoteParticipants : NO_REMOTE_VIDEOS, _visible: enabled && isFilmstripVisible(state) diff --git a/react/features/filmstrip/components/native/TileView.js b/react/features/filmstrip/components/native/TileView.js index 8822fbae8..39b19c0a5 100644 --- a/react/features/filmstrip/components/native/TileView.js +++ b/react/features/filmstrip/components/native/TileView.js @@ -11,6 +11,7 @@ import type { Dispatch } from 'redux'; import { getLocalParticipant, getParticipantCountWithFake } from '../../../base/participants'; import { connect } from '../../../base/redux'; import { setVisibleRemoteParticipants } from '../../actions.web'; +import { getDisableSelfView } from '../../functions.any'; import Thumbnail from './Thumbnail'; import styles from './styles'; @@ -30,6 +31,11 @@ type Props = { */ _columns: number, + /** + * Whether or not to hide the self view. + */ + _disableSelfView: boolean, + /** * Application's viewport height. */ @@ -221,12 +227,16 @@ class TileView extends PureComponent { * @returns {Participant[]} */ _getSortedParticipants() { - const { _localParticipant, _remoteParticipants } = this.props; + const { _localParticipant, _remoteParticipants, _disableSelfView } = this.props; if (!_localParticipant) { return EMPTY_ARRAY; } + if (_disableSelfView) { + return _remoteParticipants; + } + return [ _localParticipant?.id, ..._remoteParticipants ]; } @@ -263,12 +273,14 @@ class TileView extends PureComponent { function _mapStateToProps(state) { const responsiveUi = state['features/base/responsive-ui']; const { remoteParticipants, tileViewDimensions } = state['features/filmstrip']; + const disableSelfView = getDisableSelfView(state); const { height } = tileViewDimensions.thumbnailSize; const { columns } = tileViewDimensions; return { _aspectRatio: responsiveUi.aspectRatio, _columns: columns, + _disableSelfView: disableSelfView, _height: responsiveUi.clientHeight, _localParticipant: getLocalParticipant(state), _participantCount: getParticipantCountWithFake(state), diff --git a/react/features/filmstrip/components/web/Filmstrip.js b/react/features/filmstrip/components/web/Filmstrip.js index 77ad566d2..8f9f8600a 100644 --- a/react/features/filmstrip/components/web/Filmstrip.js +++ b/react/features/filmstrip/components/web/Filmstrip.js @@ -26,6 +26,7 @@ import { TOOLBAR_HEIGHT_MOBILE } from '../../constants'; import { shouldRemoteVideosBeVisible } from '../../functions'; +import { getDisableSelfView } from '../../functions.any'; import AudioTracksContainer from './AudioTracksContainer'; import Thumbnail from './Thumbnail'; @@ -54,6 +55,11 @@ type Props = { */ _columns: number, + /** + * Whether or not to hide the self view. + */ + _disableSelfView: boolean, + /** * The width of the filmstrip. */ @@ -189,7 +195,7 @@ class Filmstrip extends PureComponent { */ render() { const filmstripStyle = { }; - const { _currentLayout } = this.props; + const { _currentLayout, _disableSelfView } = this.props; const tileViewActive = _currentLayout === LAYOUTS.TILE_VIEW; switch (_currentLayout) { @@ -214,16 +220,18 @@ class Filmstrip extends PureComponent {
-
-
- { - !tileViewActive && - } + {!_disableSelfView && ( +
+
+ { + !tileViewActive && + } +
-
+ )} { this._renderRemoteParticipants() } @@ -301,6 +309,7 @@ class Filmstrip extends PureComponent { */ _gridItemKey({ columnIndex, rowIndex }) { const { + _disableSelfView, _columns, _iAmRecorder, _remoteParticipants, @@ -310,8 +319,8 @@ class Filmstrip extends PureComponent { const index = (rowIndex * _columns) + columnIndex; // When the thumbnails are reordered, local participant is inserted at index 0. - const localIndex = _thumbnailsReordered ? 0 : _remoteParticipantsLength; - const remoteIndex = _thumbnailsReordered && !_iAmRecorder ? index - 1 : index; + const localIndex = _thumbnailsReordered && !_disableSelfView ? 0 : _remoteParticipantsLength; + const remoteIndex = _thumbnailsReordered && !_iAmRecorder && !_disableSelfView ? index - 1 : index; if (index > _remoteParticipantsLength - (_iAmRecorder ? 1 : 0)) { return `empty-${index}`; @@ -571,6 +580,7 @@ function _mapStateToProps(state) { thumbnailSize: tileViewThumbnailSize } = state['features/filmstrip'].tileViewDimensions; const _currentLayout = getCurrentLayout(state); + const disableSelfView = getDisableSelfView(state); const { clientHeight, clientWidth } = state['features/base/responsive-ui']; const availableSpace = clientHeight - filmstripHeight; @@ -624,6 +634,7 @@ function _mapStateToProps(state) { _className: className, _columns: gridDimensions.columns, _currentLayout, + _disableSelfView: disableSelfView, _filmstripHeight: remoteFilmstripHeight, _filmstripWidth: remoteFilmstripWidth, _iAmRecorder: Boolean(iAmRecorder), diff --git a/react/features/filmstrip/components/web/ThumbnailWrapper.js b/react/features/filmstrip/components/web/ThumbnailWrapper.js index 1e4d2a7f2..d9655bb2d 100644 --- a/react/features/filmstrip/components/web/ThumbnailWrapper.js +++ b/react/features/filmstrip/components/web/ThumbnailWrapper.js @@ -4,6 +4,7 @@ import { shouldComponentUpdate } from 'react-window'; import { connect } from '../../../base/redux'; import { getCurrentLayout, LAYOUTS } from '../../../video-layout'; +import { getDisableSelfView } from '../../functions.any'; import Thumbnail from './Thumbnail'; @@ -12,6 +13,11 @@ import Thumbnail from './Thumbnail'; */ type Props = { + /** + * Whether or not to hide the self view. + */ + _disableSelfView: boolean, + /** * The horizontal offset in px for the thumbnail. Used to center the thumbnails in the last row in tile view. */ @@ -69,14 +75,14 @@ class ThumbnailWrapper extends Component { * @returns {ReactElement} */ render() { - const { _participantID, style, _horizontalOffset = 0 } = this.props; + const { _participantID, style, _horizontalOffset = 0, _disableSelfView } = this.props; if (typeof _participantID !== 'string') { return null; } if (_participantID === 'local') { - return ( + return _disableSelfView ? null : ( { + /* selector */ state => { + return { + numberOfParticipants: getParticipantCountWithFake(state), + disableSelfView: state['features/base/settings'].disableSelfView + }; + }, + /* listener */ (currentState, store) => { const state = store.getState(); if (shouldDisplayTileView(state)) { @@ -39,6 +44,8 @@ StateListenerRegistry.register( store.dispatch(setTileViewDimensions(gridDimensions)); } } + }, { + deepEquals: true }); /** diff --git a/react/features/notifications/middleware.js b/react/features/notifications/middleware.js index fcc2c1138..b8003d4d2 100644 --- a/react/features/notifications/middleware.js +++ b/react/features/notifications/middleware.js @@ -1,6 +1,6 @@ /* @flow */ -import { getCurrentConference } from '../base/conference'; +import { CONFERENCE_JOINED, getCurrentConference } from '../base/conference'; import { PARTICIPANT_JOINED, PARTICIPANT_LEFT, @@ -12,6 +12,7 @@ import { } from '../base/participants'; import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux'; import { PARTICIPANTS_PANE_OPEN } from '../participants-pane/actionTypes'; +import { openSettingsDialog, SETTINGS_TABS } from '../settings'; import { clearNotifications, @@ -31,6 +32,21 @@ import { joinLeaveNotificationsDisabled } from './functions'; */ MiddlewareRegistry.register(store => next => action => { switch (action.type) { + case CONFERENCE_JOINED: { + const { dispatch, getState } = store; + const { disableSelfView } = getState()['features/base/settings']; + + if (disableSelfView) { + dispatch(showNotification({ + titleKey: 'notify.selfViewTitle', + customActionNameKey: [ 'settings.title' ], + customActionHandler: [ () => + dispatch(openSettingsDialog(SETTINGS_TABS.PROFILE)) + ] + }, NOTIFICATION_TIMEOUT_TYPE.MEDIUM)); + } + break; + } case PARTICIPANT_JOINED: { const result = next(action); const { participant: p } = action; diff --git a/react/features/settings/actions.js b/react/features/settings/actions.js index 69a362668..e44dab768 100644 --- a/react/features/settings/actions.js +++ b/react/features/settings/actions.js @@ -1,6 +1,7 @@ // @flow import { batch } from 'react-redux'; + import { setFollowMe, setStartMutedPolicy, @@ -9,6 +10,7 @@ import { import { openDialog } from '../base/dialog'; import { i18next } from '../base/i18n'; import { updateSettings } from '../base/settings'; +import { NOTIFICATION_TIMEOUT_TYPE, showNotification } from '../notifications'; import { setPrejoinPageVisibility } from '../prejoin/actions'; import { setScreenshareFramerate } from '../screen-share/actions'; @@ -24,6 +26,8 @@ import { getSoundsTabProps } from './functions'; +import { SETTINGS_TABS } from '.'; + declare var APP: Object; /** @@ -155,6 +159,19 @@ export function submitProfileTab(newState: Object): Function { if (newState.email !== currentState.email) { APP.conference.changeLocalEmail(newState.email); } + + if (newState.disableSelfView !== currentState.disableSelfView) { + dispatch(updateSettings({ disableSelfView: newState.disableSelfView })); + if (newState.disableSelfView) { + dispatch(showNotification({ + titleKey: 'notify.selfViewTitle', + customActionNameKey: [ 'settings.title' ], + customActionHandler: [ () => + dispatch(openSettingsDialog(SETTINGS_TABS.PROFILE)) + ] + }, NOTIFICATION_TIMEOUT_TYPE.STICKY)); + } + } }; } diff --git a/react/features/settings/components/web/ProfileTab.js b/react/features/settings/components/web/ProfileTab.js index 0af6ec5ad..c7645dbf7 100644 --- a/react/features/settings/components/web/ProfileTab.js +++ b/react/features/settings/components/web/ProfileTab.js @@ -1,6 +1,7 @@ // @flow import Button from '@atlaskit/button/standard-button'; +import Checkbox from '@atlaskit/checkbox'; import { FieldTextStateless } from '@atlaskit/field-text'; import React from 'react'; @@ -32,6 +33,11 @@ export type Props = { */ authLogin: string, + /** + * Whether or not to hide the self view. + */ + disableSelfView: boolean, + /** * The display name to display for the local participant. */ @@ -77,6 +83,7 @@ class ProfileTab extends AbstractDialogTab { this._onAuthToggle = this._onAuthToggle.bind(this); this._onDisplayNameChange = this._onDisplayNameChange.bind(this); this._onEmailChange = this._onEmailChange.bind(this); + this._onChange = this._onChange.bind(this); } _onDisplayNameChange: (Object) => void; @@ -105,6 +112,19 @@ class ProfileTab extends AbstractDialogTab { super._onChange({ email: value }); } + _onChange: (Object) => void; + + /** + * Changes the disable self view state. + * + * @param {Object} e - The key event to handle. + * + * @returns {void} + */ + _onChange({ target }) { + super._onChange({ disableSelfView: target.checked }); + } + /** * Implements React's {@link Component#render()}. * @@ -115,6 +135,7 @@ class ProfileTab extends AbstractDialogTab { const { authEnabled, displayName, + disableSelfView, email, readOnlyName, t @@ -148,6 +169,12 @@ class ProfileTab extends AbstractDialogTab { value = { email } />
+
+ { authEnabled && this._renderAuth() } ); diff --git a/react/features/settings/functions.js b/react/features/settings/functions.js index 8355c9ed1..e2ff6c7af 100644 --- a/react/features/settings/functions.js +++ b/react/features/settings/functions.js @@ -156,11 +156,13 @@ export function getProfileTabProps(stateful: Object | Function) { conference } = state['features/base/conference']; const localParticipant = getLocalParticipant(state); + const { disableSelfView } = state['features/base/settings']; return { authEnabled: Boolean(conference && authEnabled), authLogin, displayName: localParticipant.name, + disableSelfView: Boolean(disableSelfView), email: localParticipant.email, readOnlyName: isNameReadOnly(state) }; diff --git a/react/features/video-layout/functions.js b/react/features/video-layout/functions.js index 1161dae58..d3cd6be74 100644 --- a/react/features/video-layout/functions.js +++ b/react/features/video-layout/functions.js @@ -15,6 +15,7 @@ import { SINGLE_COLUMN_BREAKPOINT, TWO_COLUMN_BREAKPOINT } from '../filmstrip/constants'; +import { getDisableSelfView } from '../filmstrip/functions.any'; import { isVideoPlaying } from '../shared-video/functions'; import { LAYOUTS } from './constants'; @@ -104,7 +105,10 @@ export function getTileViewGridDimensions(state: Object) { // When in tile view mode, we must discount ourselves (the local participant) because our // tile is not visible. const { iAmRecorder } = state['features/base/config']; - const numberOfParticipants = getParticipantCountWithFake(state) - (iAmRecorder ? 1 : 0); + const disableSelfView = getDisableSelfView(state); + const numberOfParticipants = getParticipantCountWithFake(state) + - (iAmRecorder ? 1 : 0) + - (disableSelfView ? 1 : 0); const columnsToMaintainASquare = Math.ceil(Math.sqrt(numberOfParticipants)); const columns = Math.min(columnsToMaintainASquare, maxColumns); diff --git a/react/features/video-menu/components/web/HideSelfViewVideoButton.js b/react/features/video-menu/components/web/HideSelfViewVideoButton.js new file mode 100644 index 000000000..7684c4f03 --- /dev/null +++ b/react/features/video-menu/components/web/HideSelfViewVideoButton.js @@ -0,0 +1,120 @@ +/* @flow */ + +import React, { PureComponent } from 'react'; + +import { translate } from '../../../base/i18n'; +import { connect } from '../../../base/redux'; +import { updateSettings } from '../../../base/settings'; +import { NOTIFICATION_TIMEOUT_TYPE, showNotification } from '../../../notifications'; +import { openSettingsDialog, SETTINGS_TABS } from '../../../settings'; + +import VideoMenuButton from './VideoMenuButton'; + +/** + * The type of the React {@code Component} props of {@link HideSelfViewVideoButton}. + */ +type Props = { + + /** + * Whether or not to hide the self view. + */ + disableSelfView: boolean, + + /** + * The redux dispatch function. + */ + dispatch: Function, + + /** + * Click handler executed aside from the main action. + */ + onClick?: Function, + + /** + * Invoked to obtain translated strings. + */ + t: Function +}; + +/** + * Implements a React {@link Component} which displays a button for hiding the local video. + * + * @augments Component + */ +class HideSelfViewVideoButton extends PureComponent { + /** + * Initializes a new {@code HideSelfViewVideoButton} 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 { + t + } = this.props; + + return ( + + ); + } + + _onClick: () => void; + + /** + * Hides the local video. + * + * @private + * @returns {void} + */ + _onClick() { + const { disableSelfView, dispatch, onClick } = this.props; + + onClick && onClick(); + dispatch(updateSettings({ + disableSelfView: !disableSelfView + })); + if (!disableSelfView) { + dispatch(showNotification({ + titleKey: 'notify.selfViewTitle', + customActionNameKey: [ 'settings.title' ], + customActionHandler: [ () => + dispatch(openSettingsDialog(SETTINGS_TABS.PROFILE)) + ] + }, NOTIFICATION_TIMEOUT_TYPE.STICKY)); + } + } +} + +/** + * Maps (parts of) the Redux state to the associated {@code FlipLocalVideoButton}'s props. + * + * @param {Object} state - The Redux state. + * @private + * @returns {Props} + */ +function _mapStateToProps(state) { + const { disableSelfView } = state['features/base/config']; + + return { + disableSelfView: Boolean(disableSelfView) + }; +} + +export default translate(connect(_mapStateToProps)(HideSelfViewVideoButton)); diff --git a/react/features/video-menu/components/web/LocalVideoMenuTriggerButton.js b/react/features/video-menu/components/web/LocalVideoMenuTriggerButton.js index c30038c8e..5b5b29b50 100644 --- a/react/features/video-menu/components/web/LocalVideoMenuTriggerButton.js +++ b/react/features/video-menu/components/web/LocalVideoMenuTriggerButton.js @@ -19,6 +19,7 @@ import { renderConnectionStatus } from '../../actions.web'; import ConnectionStatusButton from './ConnectionStatusButton'; import FlipLocalVideoButton from './FlipLocalVideoButton'; +import HideSelfViewVideoButton from './HideSelfViewVideoButton'; import VideoMenu from './VideoMenu'; @@ -131,6 +132,7 @@ class LocalVideoMenuTriggerButton extends Component { : ( + { isMobileBrowser() && }