diff --git a/lang/main.json b/lang/main.json
index dce7185c2..72d8533f7 100644
--- a/lang/main.json
+++ b/lang/main.json
@@ -1350,7 +1350,7 @@
"none": "None",
"pleaseWait": "Please wait...",
"removeBackground": "Remove background",
- "slightBlur": "Slight Blur",
+ "slightBlur": "Half Blur",
"title": "Virtual backgrounds",
"uploadedImage": "Uploaded image {{index}}",
"webAssemblyWarning": "WebAssembly not supported",
diff --git a/modules/API/API.js b/modules/API/API.js
index c86df333e..27525ec4f 100644
--- a/modules/API/API.js
+++ b/modules/API/API.js
@@ -106,6 +106,8 @@ import { startAudioScreenShareFlow, startScreenShareFlow } from '../../react/fea
import { isScreenAudioSupported } from '../../react/features/screen-share/functions';
import { toggleScreenshotCaptureSummary } from '../../react/features/screenshot-capture';
import { isScreenshotCaptureEnabled } from '../../react/features/screenshot-capture/functions';
+import SettingsDialog from '../../react/features/settings/components/web/SettingsDialog';
+import { SETTINGS_TABS } from '../../react/features/settings/constants';
import { playSharedVideo, stopSharedVideo } from '../../react/features/shared-video/actions.any';
import { extractYoutubeIdOrURL } from '../../react/features/shared-video/functions';
import { setRequestingSubtitles, toggleRequestingSubtitles } from '../../react/features/subtitles/actions';
@@ -113,7 +115,6 @@ import { isAudioMuteButtonDisabled } from '../../react/features/toolbox/function
import { setTileView, toggleTileView } from '../../react/features/video-layout';
import { muteAllParticipants } from '../../react/features/video-menu/actions';
import { setVideoQuality } from '../../react/features/video-quality';
-import VirtualBackgroundDialog from '../../react/features/virtual-background/components/VirtualBackgroundDialog';
import { getJitsiMeetTransport } from '../transport';
import { API_ID, ENDPOINT_TEXT_MESSAGE_NAME } from './constants';
@@ -798,7 +799,8 @@ function initCommands() {
APP.store.dispatch(overwriteConfig(whitelistedConfig));
},
'toggle-virtual-background': () => {
- APP.store.dispatch(toggleDialog(VirtualBackgroundDialog));
+ APP.store.dispatch(toggleDialog(SettingsDialog, {
+ defaultTab: SETTINGS_TABS.VIRTUAL_BACKGROUND }));
},
'end-conference': () => {
APP.store.dispatch(endConference());
diff --git a/react/features/base/ui/components/web/DialogWithTabs.tsx b/react/features/base/ui/components/web/DialogWithTabs.tsx
index 1f5042585..8a91913cc 100644
--- a/react/features/base/ui/components/web/DialogWithTabs.tsx
+++ b/react/features/base/ui/components/web/DialogWithTabs.tsx
@@ -146,6 +146,7 @@ interface IObject {
}
export interface IDialogTab
{
+ cancel?: Function;
className?: string;
component: ComponentType;
icon: Function;
@@ -214,7 +215,12 @@ const DialogWithTabs = ({
}
}, [ isMobile, userSelected, selectedTab ]);
- const onClose = useCallback(() => {
+ const onClose = useCallback((isCancelled = true) => {
+ if (isCancelled) {
+ tabs.forEach(({ cancel }) => {
+ cancel && dispatch(cancel());
+ });
+ }
dispatch(hideDialog());
}, []);
@@ -268,7 +274,7 @@ const DialogWithTabs = ({
tabs.forEach(({ submit }, idx) => {
submit?.(tabStates[idx]);
});
- onClose();
+ onClose(false);
}, [ tabs, tabStates ]);
const selectedTabIndex = useMemo(() => {
diff --git a/react/features/settings/actions.ts b/react/features/settings/actions.ts
index bc0704456..80a6a127e 100644
--- a/react/features/settings/actions.ts
+++ b/react/features/settings/actions.ts
@@ -11,6 +11,8 @@ import {
import { openDialog } from '../base/dialog/actions';
import i18next from '../base/i18n/i18next';
import { updateSettings } from '../base/settings/actions';
+import { toggleBackgroundEffect } from '../virtual-background/actions';
+import virtualBackgroundLogger from '../virtual-background/logger';
import {
SET_AUDIO_SETTINGS_VISIBILITY,
@@ -24,7 +26,8 @@ import {
getMoreTabProps,
getNotificationsTabProps,
getProfileTabProps,
- getShortcutsTabProps
+ getShortcutsTabProps,
+ getVirtualBackgroundTabProps
} from './functions';
/**
@@ -249,3 +252,31 @@ export function submitShortcutsTab(newState: any) {
}
};
}
+
+/**
+ * Submits the settings from the "Virtual Background" tab of the settings dialog.
+ *
+ * @param {Object} newState - The new settings.
+ * @param {boolean} isCancel - Whether the change represents a cancel.
+ * @returns {Function}
+ */
+export function submitVirtualBackgroundTab(newState: any, isCancel = false) {
+ return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
+ const currentState = getVirtualBackgroundTabProps(getState());
+
+ if (newState.options?.selectedThumbnail) {
+ await dispatch(toggleBackgroundEffect(newState.options, currentState._jitsiTrack));
+
+ if (!isCancel) {
+ // Set x scale to default value.
+ dispatch(updateSettings({
+ localFlipX: true
+ }));
+
+ virtualBackgroundLogger.info(`Virtual background type: '${
+ typeof newState.options.backgroundType === 'undefined'
+ ? 'none' : newState.options.backgroundType}' applied!`);
+ }
+ }
+ };
+}
diff --git a/react/features/settings/components/web/SettingsDialog.tsx b/react/features/settings/components/web/SettingsDialog.tsx
index 76e73f4c9..26cd9be3a 100644
--- a/react/features/settings/components/web/SettingsDialog.tsx
+++ b/react/features/settings/components/web/SettingsDialog.tsx
@@ -8,6 +8,7 @@ import {
IconCalendar,
IconGear,
IconHost,
+ IconImage,
IconShortcuts,
IconUser,
IconVideo,
@@ -24,12 +25,14 @@ import {
getAudioDeviceSelectionDialogProps,
getVideoDeviceSelectionDialogProps
} from '../../../device-selection/functions.web';
+import { checkBlurSupport } from '../../../virtual-background/functions';
import {
submitModeratorTab,
submitMoreTab,
submitNotificationsTab,
submitProfileTab,
- submitShortcutsTab
+ submitShortcutsTab,
+ submitVirtualBackgroundTab
} from '../../actions';
import { SETTINGS_TABS } from '../../constants';
import {
@@ -38,7 +41,8 @@ import {
getNotificationsMap,
getNotificationsTabProps,
getProfileTabProps,
- getShortcutsTabProps
+ getShortcutsTabProps,
+ getVirtualBackgroundTabProps
} from '../../functions';
// @ts-ignore
@@ -48,6 +52,7 @@ import MoreTab from './MoreTab';
import NotificationsTab from './NotificationsTab';
import ProfileTab from './ProfileTab';
import ShortcutsTab from './ShortcutsTab';
+import VirtualBackgroundTab from './VirtualBackgroundTab';
/**
* The type of the React {@code Component} props of
@@ -254,6 +259,7 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
const showSoundsSettings = configuredTabs.includes('sounds');
const enabledNotifications = getNotificationsMap(state);
const showNotificationsSettings = Object.keys(enabledNotifications).length > 0;
+ const virtualBackgroundSupported = checkBlurSupport();
const tabs: IDialogTab[] = [];
if (showDeviceSettings) {
@@ -305,12 +311,37 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
});
}
+ if (virtualBackgroundSupported) {
+ tabs.push({
+ name: SETTINGS_TABS.VIRTUAL_BACKGROUND,
+ component: VirtualBackgroundTab,
+ labelKey: 'virtualBackground.title',
+ props: getVirtualBackgroundTabProps(state),
+ className: `settings-pane ${classes.settingsDialog}`,
+ submit: (newState: any) => submitVirtualBackgroundTab(newState),
+ cancel: () => {
+ const { _virtualBackground } = getVirtualBackgroundTabProps(state);
+
+ return submitVirtualBackgroundTab({
+ options: {
+ backgroundType: _virtualBackground.backgroundType,
+ enabled: _virtualBackground.backgroundEffectEnabled,
+ url: _virtualBackground.virtualSource,
+ selectedThumbnail: _virtualBackground.selectedThumbnail,
+ blurValue: _virtualBackground.blurValue
+ }
+ }, true);
+ },
+ icon: IconImage
+ });
+ }
+
if (showSoundsSettings || showNotificationsSettings) {
tabs.push({
name: SETTINGS_TABS.NOTIFICATIONS,
component: NotificationsTab,
labelKey: 'settings.notifications',
- propsUpdateFunction: (tabState: any, newProps: any) => {
+ propsUpdateFunction: (tabState: any, newProps: ReturnType) => {
return {
...newProps,
enabledNotifications: tabState?.enabledNotifications || {}
@@ -373,7 +404,7 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
component: ShortcutsTab,
labelKey: 'settings.shortcuts',
props: getShortcutsTabProps(state, isDisplayedOnWelcomePage),
- propsUpdateFunction: (tabState: any, newProps: any) => {
+ propsUpdateFunction: (tabState: any, newProps: ReturnType) => {
// Updates tab props, keeping users selection
return {
diff --git a/react/features/settings/components/web/VirtualBackgroundTab.tsx b/react/features/settings/components/web/VirtualBackgroundTab.tsx
new file mode 100644
index 000000000..621aa4b8e
--- /dev/null
+++ b/react/features/settings/components/web/VirtualBackgroundTab.tsx
@@ -0,0 +1,106 @@
+import { withStyles } from '@mui/styles';
+import React from 'react';
+import { WithTranslation } from 'react-i18next';
+
+import AbstractDialogTab, {
+ IProps as AbstractDialogTabProps
+} from '../../../base/dialog/components/web/AbstractDialogTab';
+import { translate } from '../../../base/i18n/functions';
+import VirtualBackgrounds from '../../../virtual-background/components/VirtualBackgrounds';
+
+/**
+ * The type of the React {@code Component} props of {@link VirtualBackgroundTab}.
+ */
+export interface IProps extends AbstractDialogTabProps, WithTranslation {
+
+ /**
+ * Returns the jitsi track that will have background effect applied.
+ */
+ _jitsiTrack: Object;
+
+ /**
+ * CSS classes object.
+ */
+ classes: any;
+
+ /**
+ * Virtual background options.
+ */
+ options: any;
+
+ /**
+ * The selected thumbnail identifier.
+ */
+ selectedThumbnail: string;
+}
+
+const styles = () => {
+ return {
+ container: {
+ width: '100%',
+ display: 'flex',
+ flexDirection: 'column' as const
+ }
+ };
+};
+
+/**
+ * React {@code Component} for modifying language and moderator settings.
+ *
+ * @augments Component
+ */
+class VirtualBackgroundTab extends AbstractDialogTab {
+ /**
+ * Initializes a new {@code ModeratorTab} instance.
+ *
+ * @param {Object} props - The read-only properties with which the new
+ * instance is to be initialized.
+ */
+ constructor(props: IProps) {
+ super(props);
+
+ // Bind event handler so it is only bound once for every instance.
+ this._onOptionsChanged = this._onOptionsChanged.bind(this);
+ }
+
+ /**
+ * Callback invoked to select if follow-me mode
+ * should be activated.
+ *
+ * @param {Object} options - The new background options.
+ *
+ * @returns {void}
+ */
+ _onOptionsChanged(options: any) {
+ super._onChange({ options });
+ }
+
+ /**
+ * Implements React's {@link Component#render()}.
+ *
+ * @inheritdoc
+ * @returns {ReactElement}
+ */
+ render() {
+ const {
+ classes,
+ options,
+ selectedThumbnail,
+ _jitsiTrack
+ } = this.props;
+
+ return (
+
+
+
+ );
+ }
+}
+
+export default withStyles(styles)(translate(VirtualBackgroundTab));
diff --git a/react/features/settings/components/web/video/VideoSettingsContent.tsx b/react/features/settings/components/web/video/VideoSettingsContent.tsx
index 383783590..4378f2608 100644
--- a/react/features/settings/components/web/video/VideoSettingsContent.tsx
+++ b/react/features/settings/components/web/video/VideoSettingsContent.tsx
@@ -3,7 +3,6 @@ import { WithTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { IReduxState, IStore } from '../../../../app/types';
-import { openDialog } from '../../../../base/dialog/actions';
import { translate } from '../../../../base/i18n/functions';
import { IconImage } from '../../../../base/icons/svg';
import Video from '../../../../base/media/components/Video.web';
@@ -13,7 +12,8 @@ import Checkbox from '../../../../base/ui/components/web/Checkbox';
import ContextMenu from '../../../../base/ui/components/web/ContextMenu';
import ContextMenuItem from '../../../../base/ui/components/web/ContextMenuItem';
import ContextMenuItemGroup from '../../../../base/ui/components/web/ContextMenuItemGroup';
-import VirtualBackgroundDialog from '../../../../virtual-background/components/VirtualBackgroundDialog';
+import { openSettingsDialog } from '../../../actions';
+import { SETTINGS_TABS } from '../../../constants';
import { createLocalVideoTracks } from '../../../functions.web';
const videoClassName = 'video-preview-video flipVideoX';
@@ -297,7 +297,7 @@ const mapStateToProps = (state: IReduxState) => {
const mapDispatchToProps = (dispatch: IStore['dispatch']) => {
return {
- selectBackground: () => dispatch(openDialog(VirtualBackgroundDialog)),
+ selectBackground: () => dispatch(openSettingsDialog(SETTINGS_TABS.VIRTUAL_BACKGROUND)),
changeFlip: (flip: boolean) => {
dispatch(updateSettings({
localFlipX: flip
diff --git a/react/features/settings/constants.ts b/react/features/settings/constants.ts
index 69911a5e2..d71c27e00 100644
--- a/react/features/settings/constants.ts
+++ b/react/features/settings/constants.ts
@@ -6,7 +6,8 @@ export const SETTINGS_TABS = {
NOTIFICATIONS: 'notifications_tab',
PROFILE: 'profile_tab',
SHORTCUTS: 'shortcuts_tab',
- VIDEO: 'video_tab'
+ VIDEO: 'video_tab',
+ VIRTUAL_BACKGROUND: 'virtual-background_tab'
};
/**
diff --git a/react/features/settings/functions.any.ts b/react/features/settings/functions.any.ts
index 4e0276da3..33445ad9a 100644
--- a/react/features/settings/functions.any.ts
+++ b/react/features/settings/functions.any.ts
@@ -12,6 +12,7 @@ import {
} from '../base/participants/functions';
import { toState } from '../base/redux/functions';
import { getHideSelfView } from '../base/settings/functions';
+import { getLocalVideoTrack } from '../base/tracks/functions.any';
import { parseStandardURIString } from '../base/util/uri';
import { isStageFilmstripEnabled } from '../filmstrip/functions';
import { isFollowMeActive } from '../follow-me/functions';
@@ -293,3 +294,22 @@ export function getShortcutsTabProps(stateful: IStateful, isDisplayedOnWelcomePa
keyboardShortcutsEnabled: keyboardShortcut.getEnabled()
};
}
+
+/**
+ * Returns the properties for the "Virtual Background" tab from settings dialog from Redux
+ * state.
+ *
+ * @param {(Function|Object)} stateful -The (whole) redux state, or redux's
+ * {@code getState} function to be used to retrieve the state.
+ * @returns {Object} - The properties for the "Shortcuts" tab from settings
+ * dialog.
+ */
+export function getVirtualBackgroundTabProps(stateful: IStateful) {
+ const state = toState(stateful);
+
+ return {
+ _virtualBackground: state['features/virtual-background'],
+ selectedThumbnail: state['features/virtual-background'].selectedThumbnail,
+ _jitsiTrack: getLocalVideoTrack(state['features/base/tracks'])?.jitsiTrack
+ };
+}
diff --git a/react/features/virtual-background/components/UploadImageButton.tsx b/react/features/virtual-background/components/UploadImageButton.tsx
index e4310ded6..98c323ef1 100644
--- a/react/features/virtual-background/components/UploadImageButton.tsx
+++ b/react/features/virtual-background/components/UploadImageButton.tsx
@@ -6,6 +6,7 @@ import { v4 as uuidv4 } from 'uuid';
import { translate } from '../../base/i18n/functions';
import Icon from '../../base/icons/components/Icon';
import { IconPlus } from '../../base/icons/svg';
+import { withPixelLineHeight } from '../../base/styles/functions.web';
import { type Image, VIRTUAL_BACKGROUND_TYPE } from '../constants';
import { resizeImage } from '../functions';
import logger from '../logger';
@@ -40,24 +41,25 @@ interface IProps extends WithTranslation {
const useStyles = makeStyles()(theme => {
return {
+ label: {
+ ...withPixelLineHeight(theme.typography.bodyShortBold),
+ color: theme.palette.link01,
+ marginBottom: theme.spacing(3),
+ cursor: 'pointer',
+ display: 'flex',
+ alignItems: 'center'
+ },
+
addBackground: {
- marginRight: theme.spacing(2),
+ marginRight: theme.spacing(3),
+
'& svg': {
- fill: '#669aec !important'
+ fill: `${theme.palette.link01} !important`
}
},
- button: {
+
+ input: {
display: 'none'
- },
- label: {
- fontSize: '14px',
- fontWeight: 600,
- lineHeight: '20px',
- marginTop: theme.spacing(3),
- marginBottom: theme.spacing(2),
- color: '#669aec',
- display: 'inline-flex',
- cursor: 'pointer'
}
};
});
@@ -127,14 +129,14 @@ function UploadImageButton({
tabIndex = { 0 } >
{t('virtualBackground.addBackground')}
}
{
_handleClick() {
const { dispatch } = this.props;
- dispatch(openDialog(VirtualBackgroundDialog));
+ dispatch(openSettingsDialog(SETTINGS_TABS.VIRTUAL_BACKGROUND));
}
/**
diff --git a/react/features/virtual-background/components/VirtualBackgroundPreview.tsx b/react/features/virtual-background/components/VirtualBackgroundPreview.tsx
index 9dffea444..deb77632d 100644
--- a/react/features/virtual-background/components/VirtualBackgroundPreview.tsx
+++ b/react/features/virtual-background/components/VirtualBackgroundPreview.tsx
@@ -8,8 +8,6 @@ import { connect } from 'react-redux';
import { IReduxState } from '../../app/types';
import { hideDialog } from '../../base/dialog/actions';
import { translate } from '../../base/i18n/functions';
-// eslint-disable-next-line lines-around-comment
-// @ts-ignore
import Video from '../../base/media/components/Video';
import { equals } from '../../base/redux/functions';
import { getCurrentCameraDeviceId } from '../../base/settings/functions.web';
@@ -83,39 +81,28 @@ interface IState {
const styles = (theme: Theme) => {
return {
virtualBackgroundPreview: {
- '& .video-preview': {
- height: '250px'
- },
-
- '& .video-background-preview-entry': {
- height: '250px',
- width: '570px',
- marginBottom: theme.spacing(2),
- zIndex: 2,
-
- '@media (max-width: 632px)': {
- maxWidth: '336px'
- }
- },
+ height: 'auto',
+ width: '100%',
+ overflow: 'hidden',
+ marginBottom: theme.spacing(3),
+ zIndex: 2,
+ borderRadius: '3px',
+ backgroundColor: theme.palette.uiBackground,
+ position: 'relative' as const,
'& .video-preview-loader': {
- borderRadius: '6px',
- backgroundColor: 'transparent',
- height: '250px',
- marginBottom: theme.spacing(2),
- width: '572px',
- position: 'fixed',
- zIndex: 2,
+ height: '220px',
'& svg': {
- position: 'absolute',
+ position: 'absolute' as const,
top: '40%',
left: '45%'
- },
-
- '@media (min-width: 432px) and (max-width: 632px)': {
- width: '340px'
}
+ },
+
+ '& .video-preview-error': {
+ height: '220px',
+ position: 'relative'
}
}
};
@@ -238,31 +225,21 @@ class VirtualBackgroundPreview extends PureComponent {
*/
_renderPreviewEntry(data: Object) {
const { t } = this.props;
- const className = 'video-background-preview-entry';
if (this.state.loading) {
return this._loadVideoPreview();
}
if (!data) {
return (
-
-
{t('deviceSelection.previewUnavailable')}
-
+ {t('deviceSelection.previewUnavailable')}
);
}
- const props: Object = {
- className
- };
return (
-
-
-
+
);
}
@@ -310,8 +287,8 @@ class VirtualBackgroundPreview extends PureComponent {
return (
{jitsiTrack
- ?
{this._renderPreviewEntry(jitsiTrack)}
- :
{this._loadVideoPreview()}
+ ? this._renderPreviewEntry(jitsiTrack)
+ : this._loadVideoPreview()
}
);
}
}
diff --git a/react/features/virtual-background/components/VirtualBackgroundDialog.tsx b/react/features/virtual-background/components/VirtualBackgrounds.tsx
similarity index 58%
rename from react/features/virtual-background/components/VirtualBackgroundDialog.tsx
rename to react/features/virtual-background/components/VirtualBackgrounds.tsx
index d3b9a3544..dc412f841 100644
--- a/react/features/virtual-background/components/VirtualBackgroundDialog.tsx
+++ b/react/features/virtual-background/components/VirtualBackgrounds.tsx
@@ -6,27 +6,22 @@ import Bourne from '@hapi/bourne';
import { jitsiLocalStorage } from '@jitsi/js-utils/jitsi-local-storage';
import React, { useCallback, useEffect, useState } from 'react';
import { WithTranslation } from 'react-i18next';
+import { connect } from 'react-redux';
import { makeStyles } from 'tss-react/mui';
import { IReduxState } from '../../app/types';
import { getMultipleVideoSendingSupportFeatureFlag } from '../../base/config/functions.any';
-import { hideDialog } from '../../base/dialog/actions';
import { translate } from '../../base/i18n/functions';
import Icon from '../../base/icons/components/Icon';
import { IconCloseLarge } from '../../base/icons/svg';
-import { connect } from '../../base/redux/functions';
-import { updateSettings } from '../../base/settings/actions';
+import { withPixelLineHeight } from '../../base/styles/functions.web';
// @ts-ignore
import { Tooltip } from '../../base/tooltip';
-import { getLocalVideoTrack } from '../../base/tracks/functions';
-import Dialog from '../../base/ui/components/web/Dialog';
-import { toggleBackgroundEffect } from '../actions';
import { BACKGROUNDS_LIMIT, IMAGES, type Image, VIRTUAL_BACKGROUND_TYPE } from '../constants';
import { toDataURL } from '../functions';
import logger from '../logger';
import UploadImageButton from './UploadImageButton';
-// @ts-ignore
import VirtualBackgroundPreview from './VirtualBackgroundPreview';
/* eslint-enable lines-around-comment */
@@ -38,7 +33,7 @@ interface IProps extends WithTranslation {
_images: Array;
/**
- * Returns the jitsi track that will have backgraund effect applied.
+ * Returns the jitsi track that will have background effect applied.
*/
_jitsiTrack: Object;
@@ -52,11 +47,6 @@ interface IProps extends WithTranslation {
*/
_multiStreamModeEnabled: boolean;
- /**
- * Returns the selected thumbnail identifier.
- */
- _selectedThumbnail: string;
-
/**
* If the upload button should be displayed or not.
*/
@@ -78,192 +68,131 @@ interface IProps extends WithTranslation {
* NOTE: currently used only for electron in order to open the dialog in the correct state after desktop sharing
* selection.
*/
- initialOptions: Object;
+ initialOptions?: Object;
+
+ /**
+ * Options change handler.
+ */
+ onOptionsChange: Function;
+
+ /**
+ * Virtual background options.
+ */
+ options: any;
+
+ /**
+ * Returns the selected thumbnail identifier.
+ */
+ selectedThumbnail: string;
}
const onError = (event: any) => {
event.target.style.display = 'none';
};
-
-/**
- * Maps (parts of) the redux state to the associated props for the
- * {@code VirtualBackground} component.
- *
- * @param {Object} state - The Redux state.
- * @private
- * @returns {{Props}}
- */
-function _mapStateToProps(state: IReduxState): Object {
- const { localFlipX } = state['features/base/settings'];
- const dynamicBrandingImages = state['features/dynamic-branding'].virtualBackgrounds;
- const hasBrandingImages = Boolean(dynamicBrandingImages.length);
-
- return {
- _localFlipX: Boolean(localFlipX),
- _images: (hasBrandingImages && dynamicBrandingImages) || IMAGES,
- _virtualBackground: state['features/virtual-background'],
- _selectedThumbnail: state['features/virtual-background'].selectedThumbnail,
- _showUploadButton: state['features/base/config'].disableAddingBackgroundImages,
- _jitsiTrack: getLocalVideoTrack(state['features/base/tracks'])?.jitsiTrack,
- _multiStreamModeEnabled: getMultipleVideoSendingSupportFeatureFlag(state)
- };
-}
-
-const VirtualBackgroundDialog = translate(connect(_mapStateToProps)(VirtualBackground));
-
const useStyles = makeStyles()(theme => {
return {
- dialogContainer: {
- width: 'auto'
+ virtualBackgroundLoading: {
+ width: '100%',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ height: '50px'
},
+
container: {
+ width: '100%',
display: 'flex',
flexDirection: 'column'
},
- dialog: {
- alignSelf: 'flex-start',
- position: 'relative',
- maxHeight: '300px',
- color: 'white',
+
+ thumbnailContainer: {
+ width: '100%',
display: 'inline-grid',
- gridTemplateColumns: 'auto auto auto auto auto',
- columnGap: '9px',
- cursor: 'pointer',
+ gridTemplateColumns: '1fr 1fr 1fr 1fr 1fr',
+ gap: theme.spacing(1),
- // @ts-ignore
- [[ '& .desktop-share:hover',
- '& .thumbnail:hover',
- '& .blur:hover',
- '& .slight-blur:hover',
- '& .virtual-background-none:hover' ]]: {
- opacity: 0.5,
- border: '2px solid #99bbf3'
+ '@media (min-width: 608px) and (max-width: 712px)': {
+ gridTemplateColumns: '1fr 1fr 1fr 1fr'
},
- '& .background-option': {
- marginTop: theme.spacing(2),
- borderRadius: `${theme.shape.borderRadius}px`,
- height: '60px',
- width: '107px',
- textAlign: 'center',
- justifyContent: 'center',
- fontWeight: 'bold',
- boxSizing: 'border-box',
- display: 'flex',
- alignItems: 'center'
- },
- '& thumbnail-container': {
- position: 'relative',
- '&:focus-within .thumbnail ~ .delete-image-icon': {
- display: 'block'
- }
- },
- '& .thumbnail': {
- objectFit: 'cover'
- },
- '& .thumbnail:hover ~ .delete-image-icon': {
- display: 'block'
- },
- '& .thumbnail-selected': {
- objectFit: 'cover',
- border: '2px solid #246fe5'
- },
- '& .blur': {
- boxShadow: 'inset 0 0 12px #000000',
- background: '#7e8287',
- padding: '0 10px'
- },
- '& .blur-selected': {
- border: '2px solid #246fe5'
- },
- '& .slight-blur': {
- boxShadow: 'inset 0 0 12px #000000',
- background: '#a4a4a4',
- padding: '0 10px'
- },
- '& .slight-blur-selected': {
- border: '2px solid #246fe5'
- },
- '& .virtual-background-none': {
- background: '#525252',
- padding: '0 10px'
- },
- '& .none-selected': {
- border: '2px solid #246fe5'
- },
- '& .desktop-share': {
- background: '#525252'
- },
- '& .desktop-share-selected': {
- border: '2px solid #246fe5',
- padding: '0 10px'
- },
- '& delete-image-icon': {
- background: '#3d3d3d',
- position: 'absolute',
- display: 'none',
- left: '96px',
- bottom: '51px',
- '&:hover': {
- display: 'block'
- },
- '@media (max-width: 632px)': {
- left: '51px'
- }
- },
- '@media (max-width: 720px)': {
- gridTemplateColumns: 'auto auto auto auto'
- },
- '@media (max-width: 632px)': {
- gridTemplateColumns: 'auto auto auto auto auto',
- fontSize: '1.5vw',
- // @ts-ignore
- [[ '& .desktop-share:hover',
- '& .thumbnail:hover',
- '& .blur:hover',
- '& .slight-blur:hover',
- '& .virtual-background-none:hover' ]]: {
- height: '60px',
- width: '60px'
- },
-
- // @ts-ignore
- [[ '& .desktop-share',
- '& .virtual-background-none,',
- '& .thumbnail,',
- '& .blur,',
- '& .slight-blur' ]]: {
- height: '60px',
- width: '60px'
- },
-
- // @ts-ignore
- [[ '& .desktop-share-selected',
- '& .thumbnail-selected',
- '& .none-selected',
- '& .blur-selected',
- '& .slight-blur-selected' ]]: {
- height: '60px',
- width: '60px'
- }
- },
- '@media (max-width: 360px)': {
- gridTemplateColumns: 'auto auto auto auto'
- },
- '@media (max-width: 319px)': {
- gridTemplateColumns: 'auto auto'
+ '@media (max-width: 607px)': {
+ gridTemplateColumns: '1fr 1fr 1fr',
+ gap: theme.spacing(2)
}
},
- dialogMarginTop: {
- marginTop: '8px'
+
+ thumbnail: {
+ height: '54px',
+ width: '100%',
+ borderRadius: '4px',
+ boxSizing: 'border-box',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ ...withPixelLineHeight(theme.typography.labelBold),
+ color: theme.palette.text01,
+ objectFit: 'cover',
+
+ [[ '&:hover', '&:focus' ] as any]: {
+ opacity: 0.5,
+ cursor: 'pointer',
+
+ '& ~ .delete-image-icon': {
+ display: 'block'
+ }
+ },
+
+ '@media (max-width: 607px)': {
+ height: '70px'
+ }
},
- virtualBackgroundLoading: {
- overflow: 'hidden',
- position: 'fixed',
- left: '50%',
- marginTop: '10px',
- transform: 'translateX(-50%)'
+
+ selectedThumbnail: {
+ border: `2px solid ${theme.palette.action01Hover}`
+ },
+
+ noneThumbnail: {
+ backgroundColor: theme.palette.ui04
+ },
+
+ slightBlur: {
+ boxShadow: 'inset 0 0 12px #000000',
+ background: '#a4a4a4'
+ },
+
+ blur: {
+ boxShadow: 'inset 0 0 12px #000000',
+ background: '#7e8287'
+ },
+
+ storedImageContainer: {
+ position: 'relative',
+ display: 'flex',
+ flexDirection: 'column',
+
+ '&:focus-within .delete-image-container': {
+ display: 'block'
+ }
+ },
+
+ deleteImageIcon: {
+ position: 'absolute',
+ top: '3px',
+ right: '3px',
+ background: theme.palette.ui03,
+ borderRadius: '3px',
+ cursor: 'pointer',
+ display: 'none',
+
+ '@media (max-width: 607px)': {
+ display: 'block',
+ padding: '3px'
+ },
+
+ [[ '&:hover', '&:focus' ] as any]: {
+ display: 'block'
+ }
}
};
});
@@ -273,24 +202,28 @@ const useStyles = makeStyles()(theme => {
*
* @returns {ReactElement}
*/
-function VirtualBackground({
+function VirtualBackgrounds({
_images,
_jitsiTrack,
_localFlipX,
- _selectedThumbnail,
+ selectedThumbnail,
_showUploadButton,
_virtualBackground,
- dispatch,
+ onOptionsChange,
+ options,
initialOptions,
t
}: IProps) {
const { classes, cx } = useStyles();
const [ previewIsLoaded, setPreviewIsLoaded ] = useState(false);
- const [ options, setOptions ] = useState({ ...initialOptions });
const localImages = jitsiLocalStorage.getItem('virtualBackgrounds');
const [ storedImages, setStoredImages ] = useState>((localImages && Bourne.parse(localImages)) || []);
const [ loading, setLoading ] = useState(false);
- const [ initialVirtualBackground ] = useState(_virtualBackground);
+
+ useEffect(() => {
+ onOptionsChange({ ...initialOptions });
+ }, []);
+
const deleteStoredImage = useCallback(e => {
const imageId = e.currentTarget.getAttribute('data-imageid');
@@ -320,7 +253,7 @@ function VirtualBackground({
}, [ storedImages ]);
const enableBlur = useCallback(async () => {
- setOptions({
+ onOptionsChange({
backgroundType: VIRTUAL_BACKGROUND_TYPE.BLUR,
enabled: true,
blurValue: 25,
@@ -338,7 +271,7 @@ function VirtualBackground({
}, [ enableBlur ]);
const enableSlideBlur = useCallback(async () => {
- setOptions({
+ onOptionsChange({
backgroundType: VIRTUAL_BACKGROUND_TYPE.BLUR,
enabled: true,
blurValue: 8,
@@ -356,7 +289,7 @@ function VirtualBackground({
}, [ enableSlideBlur ]);
const removeBackground = useCallback(async () => {
- setOptions({
+ onOptionsChange({
enabled: false,
selectedThumbnail: 'none'
});
@@ -376,7 +309,7 @@ function VirtualBackground({
const image = storedImages.find(img => img.id === imageId);
if (image) {
- setOptions({
+ onOptionsChange({
backgroundType: 'image',
enabled: true,
url: image.src,
@@ -394,7 +327,7 @@ function VirtualBackground({
try {
const url = await toDataURL(image.src);
- setOptions({
+ onOptionsChange({
backgroundType: 'image',
enabled: true,
url,
@@ -423,48 +356,12 @@ function VirtualBackground({
}
}, [ setUploadedImageBackground ]);
- const applyVirtualBackground = useCallback(async () => {
- setLoading(true);
- await dispatch(toggleBackgroundEffect(options, _jitsiTrack));
- await setLoading(false);
-
- // Set x scale to default value.
- dispatch(updateSettings({
- localFlipX: true
- }));
-
- dispatch(hideDialog());
- logger.info(`Virtual background type: '${typeof options.backgroundType === 'undefined'
- ? 'none' : options.backgroundType}' applied!`);
- }, [ dispatch, options, _localFlipX ]);
-
- // Prevent the selection of a new virtual background if it has not been applied by default
- const cancelVirtualBackground = useCallback(async () => {
- await setOptions({
- backgroundType: initialVirtualBackground.backgroundType,
- enabled: initialVirtualBackground.backgroundEffectEnabled,
- url: initialVirtualBackground.virtualSource,
- selectedThumbnail: initialVirtualBackground.selectedThumbnail,
- blurValue: initialVirtualBackground.blurValue
- });
- dispatch(hideDialog());
- }, []);
-
const loadedPreviewState = useCallback(async loaded => {
await setPreviewIsLoaded(loaded);
}, []);
return (
-