feat(virtual-background) Move dialog to SettingsDialog tab (#13005)
Implement redesign
This commit is contained in:
parent
c8f1690057
commit
8982f17ce1
|
@ -1350,7 +1350,7 @@
|
||||||
"none": "None",
|
"none": "None",
|
||||||
"pleaseWait": "Please wait...",
|
"pleaseWait": "Please wait...",
|
||||||
"removeBackground": "Remove background",
|
"removeBackground": "Remove background",
|
||||||
"slightBlur": "Slight Blur",
|
"slightBlur": "Half Blur",
|
||||||
"title": "Virtual backgrounds",
|
"title": "Virtual backgrounds",
|
||||||
"uploadedImage": "Uploaded image {{index}}",
|
"uploadedImage": "Uploaded image {{index}}",
|
||||||
"webAssemblyWarning": "WebAssembly not supported",
|
"webAssemblyWarning": "WebAssembly not supported",
|
||||||
|
|
|
@ -106,6 +106,8 @@ import { startAudioScreenShareFlow, startScreenShareFlow } from '../../react/fea
|
||||||
import { isScreenAudioSupported } from '../../react/features/screen-share/functions';
|
import { isScreenAudioSupported } from '../../react/features/screen-share/functions';
|
||||||
import { toggleScreenshotCaptureSummary } from '../../react/features/screenshot-capture';
|
import { toggleScreenshotCaptureSummary } from '../../react/features/screenshot-capture';
|
||||||
import { isScreenshotCaptureEnabled } from '../../react/features/screenshot-capture/functions';
|
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 { playSharedVideo, stopSharedVideo } from '../../react/features/shared-video/actions.any';
|
||||||
import { extractYoutubeIdOrURL } from '../../react/features/shared-video/functions';
|
import { extractYoutubeIdOrURL } from '../../react/features/shared-video/functions';
|
||||||
import { setRequestingSubtitles, toggleRequestingSubtitles } from '../../react/features/subtitles/actions';
|
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 { setTileView, toggleTileView } from '../../react/features/video-layout';
|
||||||
import { muteAllParticipants } from '../../react/features/video-menu/actions';
|
import { muteAllParticipants } from '../../react/features/video-menu/actions';
|
||||||
import { setVideoQuality } from '../../react/features/video-quality';
|
import { setVideoQuality } from '../../react/features/video-quality';
|
||||||
import VirtualBackgroundDialog from '../../react/features/virtual-background/components/VirtualBackgroundDialog';
|
|
||||||
import { getJitsiMeetTransport } from '../transport';
|
import { getJitsiMeetTransport } from '../transport';
|
||||||
|
|
||||||
import { API_ID, ENDPOINT_TEXT_MESSAGE_NAME } from './constants';
|
import { API_ID, ENDPOINT_TEXT_MESSAGE_NAME } from './constants';
|
||||||
|
@ -798,7 +799,8 @@ function initCommands() {
|
||||||
APP.store.dispatch(overwriteConfig(whitelistedConfig));
|
APP.store.dispatch(overwriteConfig(whitelistedConfig));
|
||||||
},
|
},
|
||||||
'toggle-virtual-background': () => {
|
'toggle-virtual-background': () => {
|
||||||
APP.store.dispatch(toggleDialog(VirtualBackgroundDialog));
|
APP.store.dispatch(toggleDialog(SettingsDialog, {
|
||||||
|
defaultTab: SETTINGS_TABS.VIRTUAL_BACKGROUND }));
|
||||||
},
|
},
|
||||||
'end-conference': () => {
|
'end-conference': () => {
|
||||||
APP.store.dispatch(endConference());
|
APP.store.dispatch(endConference());
|
||||||
|
|
|
@ -146,6 +146,7 @@ interface IObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IDialogTab<P> {
|
export interface IDialogTab<P> {
|
||||||
|
cancel?: Function;
|
||||||
className?: string;
|
className?: string;
|
||||||
component: ComponentType<any>;
|
component: ComponentType<any>;
|
||||||
icon: Function;
|
icon: Function;
|
||||||
|
@ -214,7 +215,12 @@ const DialogWithTabs = ({
|
||||||
}
|
}
|
||||||
}, [ isMobile, userSelected, selectedTab ]);
|
}, [ isMobile, userSelected, selectedTab ]);
|
||||||
|
|
||||||
const onClose = useCallback(() => {
|
const onClose = useCallback((isCancel = true) => {
|
||||||
|
if (isCancel) {
|
||||||
|
tabs.forEach(({ cancel }) => {
|
||||||
|
cancel && dispatch(cancel());
|
||||||
|
});
|
||||||
|
}
|
||||||
dispatch(hideDialog());
|
dispatch(hideDialog());
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
@ -268,7 +274,7 @@ const DialogWithTabs = ({
|
||||||
tabs.forEach(({ submit }, idx) => {
|
tabs.forEach(({ submit }, idx) => {
|
||||||
submit?.(tabStates[idx]);
|
submit?.(tabStates[idx]);
|
||||||
});
|
});
|
||||||
onClose();
|
onClose(false);
|
||||||
}, [ tabs, tabStates ]);
|
}, [ tabs, tabStates ]);
|
||||||
|
|
||||||
const selectedTabIndex = useMemo(() => {
|
const selectedTabIndex = useMemo(() => {
|
||||||
|
|
|
@ -11,6 +11,8 @@ import {
|
||||||
import { openDialog } from '../base/dialog/actions';
|
import { openDialog } from '../base/dialog/actions';
|
||||||
import i18next from '../base/i18n/i18next';
|
import i18next from '../base/i18n/i18next';
|
||||||
import { updateSettings } from '../base/settings/actions';
|
import { updateSettings } from '../base/settings/actions';
|
||||||
|
import { toggleBackgroundEffect } from '../virtual-background/actions';
|
||||||
|
import virtualBackgroundLogger from '../virtual-background/logger';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
SET_AUDIO_SETTINGS_VISIBILITY,
|
SET_AUDIO_SETTINGS_VISIBILITY,
|
||||||
|
@ -24,7 +26,8 @@ import {
|
||||||
getMoreTabProps,
|
getMoreTabProps,
|
||||||
getNotificationsTabProps,
|
getNotificationsTabProps,
|
||||||
getProfileTabProps,
|
getProfileTabProps,
|
||||||
getShortcutsTabProps
|
getShortcutsTabProps,
|
||||||
|
getVirtualBackgroundTabProps
|
||||||
} from './functions';
|
} 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!`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
IconCalendar,
|
IconCalendar,
|
||||||
IconGear,
|
IconGear,
|
||||||
IconHost,
|
IconHost,
|
||||||
|
IconImage,
|
||||||
IconShortcuts,
|
IconShortcuts,
|
||||||
IconUser,
|
IconUser,
|
||||||
IconVideo,
|
IconVideo,
|
||||||
|
@ -24,12 +25,14 @@ import {
|
||||||
getAudioDeviceSelectionDialogProps,
|
getAudioDeviceSelectionDialogProps,
|
||||||
getVideoDeviceSelectionDialogProps
|
getVideoDeviceSelectionDialogProps
|
||||||
} from '../../../device-selection/functions.web';
|
} from '../../../device-selection/functions.web';
|
||||||
|
import { checkBlurSupport } from '../../../virtual-background/functions';
|
||||||
import {
|
import {
|
||||||
submitModeratorTab,
|
submitModeratorTab,
|
||||||
submitMoreTab,
|
submitMoreTab,
|
||||||
submitNotificationsTab,
|
submitNotificationsTab,
|
||||||
submitProfileTab,
|
submitProfileTab,
|
||||||
submitShortcutsTab
|
submitShortcutsTab,
|
||||||
|
submitVirtualBackgroundTab
|
||||||
} from '../../actions';
|
} from '../../actions';
|
||||||
import { SETTINGS_TABS } from '../../constants';
|
import { SETTINGS_TABS } from '../../constants';
|
||||||
import {
|
import {
|
||||||
|
@ -38,7 +41,8 @@ import {
|
||||||
getNotificationsMap,
|
getNotificationsMap,
|
||||||
getNotificationsTabProps,
|
getNotificationsTabProps,
|
||||||
getProfileTabProps,
|
getProfileTabProps,
|
||||||
getShortcutsTabProps
|
getShortcutsTabProps,
|
||||||
|
getVirtualBackgroundTabProps
|
||||||
} from '../../functions';
|
} from '../../functions';
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -48,6 +52,7 @@ import MoreTab from './MoreTab';
|
||||||
import NotificationsTab from './NotificationsTab';
|
import NotificationsTab from './NotificationsTab';
|
||||||
import ProfileTab from './ProfileTab';
|
import ProfileTab from './ProfileTab';
|
||||||
import ShortcutsTab from './ShortcutsTab';
|
import ShortcutsTab from './ShortcutsTab';
|
||||||
|
import VirtualBackgroundTab from './VirtualBackgroundTab';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of the React {@code Component} props of
|
* 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 showSoundsSettings = configuredTabs.includes('sounds');
|
||||||
const enabledNotifications = getNotificationsMap(state);
|
const enabledNotifications = getNotificationsMap(state);
|
||||||
const showNotificationsSettings = Object.keys(enabledNotifications).length > 0;
|
const showNotificationsSettings = Object.keys(enabledNotifications).length > 0;
|
||||||
|
const virtualBackgroundSupported = checkBlurSupport();
|
||||||
const tabs: IDialogTab<any>[] = [];
|
const tabs: IDialogTab<any>[] = [];
|
||||||
|
|
||||||
if (showDeviceSettings) {
|
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) {
|
if (showSoundsSettings || showNotificationsSettings) {
|
||||||
tabs.push({
|
tabs.push({
|
||||||
name: SETTINGS_TABS.NOTIFICATIONS,
|
name: SETTINGS_TABS.NOTIFICATIONS,
|
||||||
component: NotificationsTab,
|
component: NotificationsTab,
|
||||||
labelKey: 'settings.notifications',
|
labelKey: 'settings.notifications',
|
||||||
propsUpdateFunction: (tabState: any, newProps: any) => {
|
propsUpdateFunction: (tabState: any, newProps: ReturnType<typeof getNotificationsTabProps>) => {
|
||||||
return {
|
return {
|
||||||
...newProps,
|
...newProps,
|
||||||
enabledNotifications: tabState?.enabledNotifications || {}
|
enabledNotifications: tabState?.enabledNotifications || {}
|
||||||
|
@ -373,7 +404,7 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||||
component: ShortcutsTab,
|
component: ShortcutsTab,
|
||||||
labelKey: 'settings.shortcuts',
|
labelKey: 'settings.shortcuts',
|
||||||
props: getShortcutsTabProps(state, isDisplayedOnWelcomePage),
|
props: getShortcutsTabProps(state, isDisplayedOnWelcomePage),
|
||||||
propsUpdateFunction: (tabState: any, newProps: any) => {
|
propsUpdateFunction: (tabState: any, newProps: ReturnType<typeof getShortcutsTabProps>) => {
|
||||||
// Updates tab props, keeping users selection
|
// Updates tab props, keeping users selection
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
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<IProps, any> {
|
||||||
|
/**
|
||||||
|
* 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 (
|
||||||
|
<div
|
||||||
|
className = { classes.container }
|
||||||
|
id = 'virtual-background-dialog'
|
||||||
|
key = 'virtual-background'>
|
||||||
|
<VirtualBackgrounds
|
||||||
|
_jitsiTrack = { _jitsiTrack }
|
||||||
|
onOptionsChange = { this._onOptionsChanged }
|
||||||
|
options = { options }
|
||||||
|
selectedThumbnail = { selectedThumbnail } />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withStyles(styles)(translate(VirtualBackgroundTab));
|
|
@ -3,7 +3,6 @@ import { WithTranslation } from 'react-i18next';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { IReduxState, IStore } from '../../../../app/types';
|
import { IReduxState, IStore } from '../../../../app/types';
|
||||||
import { openDialog } from '../../../../base/dialog/actions';
|
|
||||||
import { translate } from '../../../../base/i18n/functions';
|
import { translate } from '../../../../base/i18n/functions';
|
||||||
import { IconImage } from '../../../../base/icons/svg';
|
import { IconImage } from '../../../../base/icons/svg';
|
||||||
import Video from '../../../../base/media/components/Video.web';
|
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 ContextMenu from '../../../../base/ui/components/web/ContextMenu';
|
||||||
import ContextMenuItem from '../../../../base/ui/components/web/ContextMenuItem';
|
import ContextMenuItem from '../../../../base/ui/components/web/ContextMenuItem';
|
||||||
import ContextMenuItemGroup from '../../../../base/ui/components/web/ContextMenuItemGroup';
|
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';
|
import { createLocalVideoTracks } from '../../../functions.web';
|
||||||
|
|
||||||
const videoClassName = 'video-preview-video flipVideoX';
|
const videoClassName = 'video-preview-video flipVideoX';
|
||||||
|
@ -297,7 +297,7 @@ const mapStateToProps = (state: IReduxState) => {
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch: IStore['dispatch']) => {
|
const mapDispatchToProps = (dispatch: IStore['dispatch']) => {
|
||||||
return {
|
return {
|
||||||
selectBackground: () => dispatch(openDialog(VirtualBackgroundDialog)),
|
selectBackground: () => dispatch(openSettingsDialog(SETTINGS_TABS.VIRTUAL_BACKGROUND)),
|
||||||
changeFlip: (flip: boolean) => {
|
changeFlip: (flip: boolean) => {
|
||||||
dispatch(updateSettings({
|
dispatch(updateSettings({
|
||||||
localFlipX: flip
|
localFlipX: flip
|
||||||
|
|
|
@ -6,7 +6,8 @@ export const SETTINGS_TABS = {
|
||||||
NOTIFICATIONS: 'notifications_tab',
|
NOTIFICATIONS: 'notifications_tab',
|
||||||
PROFILE: 'profile_tab',
|
PROFILE: 'profile_tab',
|
||||||
SHORTCUTS: 'shortcuts_tab',
|
SHORTCUTS: 'shortcuts_tab',
|
||||||
VIDEO: 'video_tab'
|
VIDEO: 'video_tab',
|
||||||
|
VIRTUAL_BACKGROUND: 'virtual-background_tab'
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {
|
||||||
} from '../base/participants/functions';
|
} from '../base/participants/functions';
|
||||||
import { toState } from '../base/redux/functions';
|
import { toState } from '../base/redux/functions';
|
||||||
import { getHideSelfView } from '../base/settings/functions';
|
import { getHideSelfView } from '../base/settings/functions';
|
||||||
|
import { getLocalVideoTrack } from '../base/tracks/functions.any';
|
||||||
import { parseStandardURIString } from '../base/util/uri';
|
import { parseStandardURIString } from '../base/util/uri';
|
||||||
import { isStageFilmstripEnabled } from '../filmstrip/functions';
|
import { isStageFilmstripEnabled } from '../filmstrip/functions';
|
||||||
import { isFollowMeActive } from '../follow-me/functions';
|
import { isFollowMeActive } from '../follow-me/functions';
|
||||||
|
@ -293,3 +294,22 @@ export function getShortcutsTabProps(stateful: IStateful, isDisplayedOnWelcomePa
|
||||||
keyboardShortcutsEnabled: keyboardShortcut.getEnabled()
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { v4 as uuidv4 } from 'uuid';
|
||||||
import { translate } from '../../base/i18n/functions';
|
import { translate } from '../../base/i18n/functions';
|
||||||
import Icon from '../../base/icons/components/Icon';
|
import Icon from '../../base/icons/components/Icon';
|
||||||
import { IconPlus } from '../../base/icons/svg';
|
import { IconPlus } from '../../base/icons/svg';
|
||||||
|
import { withPixelLineHeight } from '../../base/styles/functions.web';
|
||||||
import { type Image, VIRTUAL_BACKGROUND_TYPE } from '../constants';
|
import { type Image, VIRTUAL_BACKGROUND_TYPE } from '../constants';
|
||||||
import { resizeImage } from '../functions';
|
import { resizeImage } from '../functions';
|
||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
|
@ -40,24 +41,25 @@ interface IProps extends WithTranslation {
|
||||||
|
|
||||||
const useStyles = makeStyles()(theme => {
|
const useStyles = makeStyles()(theme => {
|
||||||
return {
|
return {
|
||||||
|
label: {
|
||||||
|
...withPixelLineHeight(theme.typography.bodyShortBold),
|
||||||
|
color: theme.palette.link01,
|
||||||
|
marginBottom: theme.spacing(3),
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center'
|
||||||
|
},
|
||||||
|
|
||||||
addBackground: {
|
addBackground: {
|
||||||
marginRight: theme.spacing(2),
|
marginRight: theme.spacing(3),
|
||||||
|
|
||||||
'& svg': {
|
'& svg': {
|
||||||
fill: '#669aec !important'
|
fill: `${theme.palette.link01} !important`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
button: {
|
|
||||||
|
input: {
|
||||||
display: 'none'
|
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 } >
|
tabIndex = { 0 } >
|
||||||
<Icon
|
<Icon
|
||||||
className = { classes.addBackground }
|
className = { classes.addBackground }
|
||||||
size = { 20 }
|
size = { 24 }
|
||||||
src = { IconPlus } />
|
src = { IconPlus } />
|
||||||
{t('virtualBackground.addBackground')}
|
{t('virtualBackground.addBackground')}
|
||||||
</label>}
|
</label>}
|
||||||
|
|
||||||
<input
|
<input
|
||||||
accept = 'image/*'
|
accept = 'image/*'
|
||||||
className = { classes.button }
|
className = { classes.input }
|
||||||
id = 'file-upload'
|
id = 'file-upload'
|
||||||
onChange = { uploadImage }
|
onChange = { uploadImage }
|
||||||
ref = { uploadImageButton }
|
ref = { uploadImageButton }
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import { openDialog } from '../../base/dialog';
|
|
||||||
import { translate } from '../../base/i18n';
|
import { translate } from '../../base/i18n';
|
||||||
import { IconImage } from '../../base/icons';
|
import { IconImage } from '../../base/icons';
|
||||||
import { connect } from '../../base/redux';
|
import { connect } from '../../base/redux';
|
||||||
import { AbstractButton } from '../../base/toolbox/components';
|
import { AbstractButton } from '../../base/toolbox/components';
|
||||||
import type { AbstractButtonProps } from '../../base/toolbox/components';
|
import type { AbstractButtonProps } from '../../base/toolbox/components';
|
||||||
|
import { openSettingsDialog } from '../../settings/actions';
|
||||||
|
import { SETTINGS_TABS } from '../../settings/constants';
|
||||||
import { checkBlurSupport } from '../functions';
|
import { checkBlurSupport } from '../functions';
|
||||||
|
|
||||||
import { VirtualBackgroundDialog } from './index';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of the React {@code Component} props of {@link VideoBackgroundButton}.
|
* The type of the React {@code Component} props of {@link VideoBackgroundButton}.
|
||||||
*/
|
*/
|
||||||
|
@ -45,7 +44,7 @@ class VideoBackgroundButton extends AbstractButton<Props, *> {
|
||||||
_handleClick() {
|
_handleClick() {
|
||||||
const { dispatch } = this.props;
|
const { dispatch } = this.props;
|
||||||
|
|
||||||
dispatch(openDialog(VirtualBackgroundDialog));
|
dispatch(openSettingsDialog(SETTINGS_TABS.VIRTUAL_BACKGROUND));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -8,8 +8,6 @@ import { connect } from 'react-redux';
|
||||||
import { IReduxState } from '../../app/types';
|
import { IReduxState } from '../../app/types';
|
||||||
import { hideDialog } from '../../base/dialog/actions';
|
import { hideDialog } from '../../base/dialog/actions';
|
||||||
import { translate } from '../../base/i18n/functions';
|
import { translate } from '../../base/i18n/functions';
|
||||||
// eslint-disable-next-line lines-around-comment
|
|
||||||
// @ts-ignore
|
|
||||||
import Video from '../../base/media/components/Video';
|
import Video from '../../base/media/components/Video';
|
||||||
import { equals } from '../../base/redux/functions';
|
import { equals } from '../../base/redux/functions';
|
||||||
import { getCurrentCameraDeviceId } from '../../base/settings/functions.web';
|
import { getCurrentCameraDeviceId } from '../../base/settings/functions.web';
|
||||||
|
@ -83,39 +81,28 @@ interface IState {
|
||||||
const styles = (theme: Theme) => {
|
const styles = (theme: Theme) => {
|
||||||
return {
|
return {
|
||||||
virtualBackgroundPreview: {
|
virtualBackgroundPreview: {
|
||||||
'& .video-preview': {
|
height: 'auto',
|
||||||
height: '250px'
|
width: '100%',
|
||||||
},
|
overflow: 'hidden',
|
||||||
|
marginBottom: theme.spacing(3),
|
||||||
'& .video-background-preview-entry': {
|
zIndex: 2,
|
||||||
height: '250px',
|
borderRadius: '3px',
|
||||||
width: '570px',
|
backgroundColor: theme.palette.uiBackground,
|
||||||
marginBottom: theme.spacing(2),
|
position: 'relative' as const,
|
||||||
zIndex: 2,
|
|
||||||
|
|
||||||
'@media (max-width: 632px)': {
|
|
||||||
maxWidth: '336px'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
'& .video-preview-loader': {
|
'& .video-preview-loader': {
|
||||||
borderRadius: '6px',
|
height: '220px',
|
||||||
backgroundColor: 'transparent',
|
|
||||||
height: '250px',
|
|
||||||
marginBottom: theme.spacing(2),
|
|
||||||
width: '572px',
|
|
||||||
position: 'fixed',
|
|
||||||
zIndex: 2,
|
|
||||||
|
|
||||||
'& svg': {
|
'& svg': {
|
||||||
position: 'absolute',
|
position: 'absolute' as const,
|
||||||
top: '40%',
|
top: '40%',
|
||||||
left: '45%'
|
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<IProps, IState> {
|
||||||
*/
|
*/
|
||||||
_renderPreviewEntry(data: Object) {
|
_renderPreviewEntry(data: Object) {
|
||||||
const { t } = this.props;
|
const { t } = this.props;
|
||||||
const className = 'video-background-preview-entry';
|
|
||||||
|
|
||||||
if (this.state.loading) {
|
if (this.state.loading) {
|
||||||
return this._loadVideoPreview();
|
return this._loadVideoPreview();
|
||||||
}
|
}
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div className = 'video-preview-error'>{t('deviceSelection.previewUnavailable')}</div>
|
||||||
className = { className }
|
|
||||||
video-preview-container = { true }>
|
|
||||||
<div className = 'video-preview-error'>{t('deviceSelection.previewUnavailable')}</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const props: Object = {
|
|
||||||
className
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div { ...props }>
|
<Video
|
||||||
<Video
|
className = { videoClassName }
|
||||||
className = { videoClassName }
|
playsinline = { true }
|
||||||
playsinline = { true }
|
videoTrack = {{ jitsiTrack: data }} />
|
||||||
videoTrack = {{ jitsiTrack: data }} />
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -310,8 +287,8 @@ class VirtualBackgroundPreview extends PureComponent<IProps, IState> {
|
||||||
|
|
||||||
return (<div className = { classes.virtualBackgroundPreview }>
|
return (<div className = { classes.virtualBackgroundPreview }>
|
||||||
{jitsiTrack
|
{jitsiTrack
|
||||||
? <div className = 'video-preview'>{this._renderPreviewEntry(jitsiTrack)}</div>
|
? this._renderPreviewEntry(jitsiTrack)
|
||||||
: <div className = 'video-preview-loader'>{this._loadVideoPreview()}</div>
|
: this._loadVideoPreview()
|
||||||
}</div>);
|
}</div>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,27 +6,22 @@ import Bourne from '@hapi/bourne';
|
||||||
import { jitsiLocalStorage } from '@jitsi/js-utils/jitsi-local-storage';
|
import { jitsiLocalStorage } from '@jitsi/js-utils/jitsi-local-storage';
|
||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { WithTranslation } from 'react-i18next';
|
import { WithTranslation } from 'react-i18next';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import { makeStyles } from 'tss-react/mui';
|
import { makeStyles } from 'tss-react/mui';
|
||||||
|
|
||||||
import { IReduxState } from '../../app/types';
|
import { IReduxState } from '../../app/types';
|
||||||
import { getMultipleVideoSendingSupportFeatureFlag } from '../../base/config/functions.any';
|
import { getMultipleVideoSendingSupportFeatureFlag } from '../../base/config/functions.any';
|
||||||
import { hideDialog } from '../../base/dialog/actions';
|
|
||||||
import { translate } from '../../base/i18n/functions';
|
import { translate } from '../../base/i18n/functions';
|
||||||
import Icon from '../../base/icons/components/Icon';
|
import Icon from '../../base/icons/components/Icon';
|
||||||
import { IconCloseLarge } from '../../base/icons/svg';
|
import { IconCloseLarge } from '../../base/icons/svg';
|
||||||
import { connect } from '../../base/redux/functions';
|
import { withPixelLineHeight } from '../../base/styles/functions.web';
|
||||||
import { updateSettings } from '../../base/settings/actions';
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { Tooltip } from '../../base/tooltip';
|
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 { BACKGROUNDS_LIMIT, IMAGES, type Image, VIRTUAL_BACKGROUND_TYPE } from '../constants';
|
||||||
import { toDataURL } from '../functions';
|
import { toDataURL } from '../functions';
|
||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
|
|
||||||
import UploadImageButton from './UploadImageButton';
|
import UploadImageButton from './UploadImageButton';
|
||||||
// @ts-ignore
|
|
||||||
import VirtualBackgroundPreview from './VirtualBackgroundPreview';
|
import VirtualBackgroundPreview from './VirtualBackgroundPreview';
|
||||||
/* eslint-enable lines-around-comment */
|
/* eslint-enable lines-around-comment */
|
||||||
|
|
||||||
|
@ -38,7 +33,7 @@ interface IProps extends WithTranslation {
|
||||||
_images: Array<Image>;
|
_images: Array<Image>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the jitsi track that will have backgraund effect applied.
|
* Returns the jitsi track that will have background effect applied.
|
||||||
*/
|
*/
|
||||||
_jitsiTrack: Object;
|
_jitsiTrack: Object;
|
||||||
|
|
||||||
|
@ -52,11 +47,6 @@ interface IProps extends WithTranslation {
|
||||||
*/
|
*/
|
||||||
_multiStreamModeEnabled: boolean;
|
_multiStreamModeEnabled: boolean;
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the selected thumbnail identifier.
|
|
||||||
*/
|
|
||||||
_selectedThumbnail: string;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the upload button should be displayed or not.
|
* 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
|
* NOTE: currently used only for electron in order to open the dialog in the correct state after desktop sharing
|
||||||
* selection.
|
* 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) => {
|
const onError = (event: any) => {
|
||||||
event.target.style.display = 'none';
|
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 => {
|
const useStyles = makeStyles()(theme => {
|
||||||
return {
|
return {
|
||||||
dialogContainer: {
|
virtualBackgroundLoading: {
|
||||||
width: 'auto'
|
width: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
height: '50px'
|
||||||
},
|
},
|
||||||
|
|
||||||
container: {
|
container: {
|
||||||
|
width: '100%',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column'
|
flexDirection: 'column'
|
||||||
},
|
},
|
||||||
dialog: {
|
|
||||||
alignSelf: 'flex-start',
|
thumbnailContainer: {
|
||||||
position: 'relative',
|
width: '100%',
|
||||||
maxHeight: '300px',
|
|
||||||
color: 'white',
|
|
||||||
display: 'inline-grid',
|
display: 'inline-grid',
|
||||||
gridTemplateColumns: 'auto auto auto auto auto',
|
gridTemplateColumns: '1fr 1fr 1fr 1fr 1fr',
|
||||||
columnGap: '9px',
|
gap: theme.spacing(1),
|
||||||
cursor: 'pointer',
|
|
||||||
|
|
||||||
// @ts-ignore
|
'@media (min-width: 608px) and (max-width: 712px)': {
|
||||||
[[ '& .desktop-share:hover',
|
gridTemplateColumns: '1fr 1fr 1fr 1fr'
|
||||||
'& .thumbnail:hover',
|
|
||||||
'& .blur:hover',
|
|
||||||
'& .slight-blur:hover',
|
|
||||||
'& .virtual-background-none:hover' ]]: {
|
|
||||||
opacity: 0.5,
|
|
||||||
border: '2px solid #99bbf3'
|
|
||||||
},
|
},
|
||||||
'& .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
|
'@media (max-width: 607px)': {
|
||||||
[[ '& .desktop-share:hover',
|
gridTemplateColumns: '1fr 1fr 1fr',
|
||||||
'& .thumbnail:hover',
|
gap: theme.spacing(2)
|
||||||
'& .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'
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
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',
|
selectedThumbnail: {
|
||||||
position: 'fixed',
|
border: `2px solid ${theme.palette.action01Hover}`
|
||||||
left: '50%',
|
},
|
||||||
marginTop: '10px',
|
|
||||||
transform: 'translateX(-50%)'
|
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}
|
* @returns {ReactElement}
|
||||||
*/
|
*/
|
||||||
function VirtualBackground({
|
function VirtualBackgrounds({
|
||||||
_images,
|
_images,
|
||||||
_jitsiTrack,
|
_jitsiTrack,
|
||||||
_localFlipX,
|
_localFlipX,
|
||||||
_selectedThumbnail,
|
selectedThumbnail,
|
||||||
_showUploadButton,
|
_showUploadButton,
|
||||||
_virtualBackground,
|
_virtualBackground,
|
||||||
dispatch,
|
onOptionsChange,
|
||||||
|
options,
|
||||||
initialOptions,
|
initialOptions,
|
||||||
t
|
t
|
||||||
}: IProps) {
|
}: IProps) {
|
||||||
const { classes, cx } = useStyles();
|
const { classes, cx } = useStyles();
|
||||||
const [ previewIsLoaded, setPreviewIsLoaded ] = useState(false);
|
const [ previewIsLoaded, setPreviewIsLoaded ] = useState(false);
|
||||||
const [ options, setOptions ] = useState<any>({ ...initialOptions });
|
|
||||||
const localImages = jitsiLocalStorage.getItem('virtualBackgrounds');
|
const localImages = jitsiLocalStorage.getItem('virtualBackgrounds');
|
||||||
const [ storedImages, setStoredImages ] = useState<Array<Image>>((localImages && Bourne.parse(localImages)) || []);
|
const [ storedImages, setStoredImages ] = useState<Array<Image>>((localImages && Bourne.parse(localImages)) || []);
|
||||||
const [ loading, setLoading ] = useState(false);
|
const [ loading, setLoading ] = useState(false);
|
||||||
const [ initialVirtualBackground ] = useState(_virtualBackground);
|
|
||||||
|
useEffect(() => {
|
||||||
|
onOptionsChange({ ...initialOptions });
|
||||||
|
}, []);
|
||||||
|
|
||||||
const deleteStoredImage = useCallback(e => {
|
const deleteStoredImage = useCallback(e => {
|
||||||
const imageId = e.currentTarget.getAttribute('data-imageid');
|
const imageId = e.currentTarget.getAttribute('data-imageid');
|
||||||
|
|
||||||
|
@ -320,7 +253,7 @@ function VirtualBackground({
|
||||||
}, [ storedImages ]);
|
}, [ storedImages ]);
|
||||||
|
|
||||||
const enableBlur = useCallback(async () => {
|
const enableBlur = useCallback(async () => {
|
||||||
setOptions({
|
onOptionsChange({
|
||||||
backgroundType: VIRTUAL_BACKGROUND_TYPE.BLUR,
|
backgroundType: VIRTUAL_BACKGROUND_TYPE.BLUR,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
blurValue: 25,
|
blurValue: 25,
|
||||||
|
@ -338,7 +271,7 @@ function VirtualBackground({
|
||||||
}, [ enableBlur ]);
|
}, [ enableBlur ]);
|
||||||
|
|
||||||
const enableSlideBlur = useCallback(async () => {
|
const enableSlideBlur = useCallback(async () => {
|
||||||
setOptions({
|
onOptionsChange({
|
||||||
backgroundType: VIRTUAL_BACKGROUND_TYPE.BLUR,
|
backgroundType: VIRTUAL_BACKGROUND_TYPE.BLUR,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
blurValue: 8,
|
blurValue: 8,
|
||||||
|
@ -356,7 +289,7 @@ function VirtualBackground({
|
||||||
}, [ enableSlideBlur ]);
|
}, [ enableSlideBlur ]);
|
||||||
|
|
||||||
const removeBackground = useCallback(async () => {
|
const removeBackground = useCallback(async () => {
|
||||||
setOptions({
|
onOptionsChange({
|
||||||
enabled: false,
|
enabled: false,
|
||||||
selectedThumbnail: 'none'
|
selectedThumbnail: 'none'
|
||||||
});
|
});
|
||||||
|
@ -376,7 +309,7 @@ function VirtualBackground({
|
||||||
const image = storedImages.find(img => img.id === imageId);
|
const image = storedImages.find(img => img.id === imageId);
|
||||||
|
|
||||||
if (image) {
|
if (image) {
|
||||||
setOptions({
|
onOptionsChange({
|
||||||
backgroundType: 'image',
|
backgroundType: 'image',
|
||||||
enabled: true,
|
enabled: true,
|
||||||
url: image.src,
|
url: image.src,
|
||||||
|
@ -394,7 +327,7 @@ function VirtualBackground({
|
||||||
try {
|
try {
|
||||||
const url = await toDataURL(image.src);
|
const url = await toDataURL(image.src);
|
||||||
|
|
||||||
setOptions({
|
onOptionsChange({
|
||||||
backgroundType: 'image',
|
backgroundType: 'image',
|
||||||
enabled: true,
|
enabled: true,
|
||||||
url,
|
url,
|
||||||
|
@ -423,48 +356,12 @@ function VirtualBackground({
|
||||||
}
|
}
|
||||||
}, [ setUploadedImageBackground ]);
|
}, [ 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 => {
|
const loadedPreviewState = useCallback(async loaded => {
|
||||||
await setPreviewIsLoaded(loaded);
|
await setPreviewIsLoaded(loaded);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<>
|
||||||
className = { classes.dialogContainer }
|
|
||||||
ok = {{
|
|
||||||
disabled: !options || loading || !previewIsLoaded,
|
|
||||||
translationKey: 'virtualBackground.apply'
|
|
||||||
}}
|
|
||||||
onCancel = { cancelVirtualBackground }
|
|
||||||
onSubmit = { applyVirtualBackground }
|
|
||||||
size = 'large'
|
|
||||||
titleKey = 'virtualBackground.title' >
|
|
||||||
<VirtualBackgroundPreview
|
<VirtualBackgroundPreview
|
||||||
loadedPreview = { loadedPreviewState }
|
loadedPreview = { loadedPreviewState }
|
||||||
options = { options } />
|
options = { options } />
|
||||||
|
@ -481,23 +378,22 @@ function VirtualBackground({
|
||||||
{_showUploadButton
|
{_showUploadButton
|
||||||
&& <UploadImageButton
|
&& <UploadImageButton
|
||||||
setLoading = { setLoading }
|
setLoading = { setLoading }
|
||||||
setOptions = { setOptions }
|
setOptions = { onOptionsChange }
|
||||||
setStoredImages = { setStoredImages }
|
setStoredImages = { setStoredImages }
|
||||||
showLabel = { previewIsLoaded }
|
showLabel = { previewIsLoaded }
|
||||||
storedImages = { storedImages } />}
|
storedImages = { storedImages } />}
|
||||||
<div
|
<div
|
||||||
className = { cx(classes.dialog, { [classes.dialogMarginTop]: previewIsLoaded }) }
|
className = { classes.thumbnailContainer }
|
||||||
role = 'radiogroup'
|
role = 'radiogroup'
|
||||||
tabIndex = { -1 }>
|
tabIndex = { -1 }>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content = { t('virtualBackground.removeBackground') }
|
content = { t('virtualBackground.removeBackground') }
|
||||||
position = { 'top' }>
|
position = { 'top' }>
|
||||||
<div
|
<div
|
||||||
aria-checked = { _selectedThumbnail === 'none' }
|
aria-checked = { selectedThumbnail === 'none' }
|
||||||
aria-label = { t('virtualBackground.removeBackground') }
|
aria-label = { t('virtualBackground.removeBackground') }
|
||||||
className = { cx('background-option', 'virtual-background-none', {
|
className = { cx(classes.thumbnail, classes.noneThumbnail,
|
||||||
'none-selected': _selectedThumbnail === 'none'
|
selectedThumbnail === 'none' && classes.selectedThumbnail) }
|
||||||
}) }
|
|
||||||
onClick = { removeBackground }
|
onClick = { removeBackground }
|
||||||
onKeyPress = { removeBackgroundKeyPress }
|
onKeyPress = { removeBackgroundKeyPress }
|
||||||
role = 'radio'
|
role = 'radio'
|
||||||
|
@ -509,11 +405,10 @@ function VirtualBackground({
|
||||||
content = { t('virtualBackground.slightBlur') }
|
content = { t('virtualBackground.slightBlur') }
|
||||||
position = { 'top' }>
|
position = { 'top' }>
|
||||||
<div
|
<div
|
||||||
aria-checked = { _selectedThumbnail === 'slight-blur' }
|
aria-checked = { selectedThumbnail === 'slight-blur' }
|
||||||
aria-label = { t('virtualBackground.slightBlur') }
|
aria-label = { t('virtualBackground.slightBlur') }
|
||||||
className = { cx('background-option', 'slight-blur', {
|
className = { cx(classes.thumbnail, classes.slightBlur,
|
||||||
'slight-blur-selected': _selectedThumbnail === 'slight-blur'
|
selectedThumbnail === 'slight-blur' && classes.selectedThumbnail) }
|
||||||
}) }
|
|
||||||
onClick = { enableSlideBlur }
|
onClick = { enableSlideBlur }
|
||||||
onKeyPress = { enableSlideBlurKeyPress }
|
onKeyPress = { enableSlideBlurKeyPress }
|
||||||
role = 'radio'
|
role = 'radio'
|
||||||
|
@ -525,11 +420,10 @@ function VirtualBackground({
|
||||||
content = { t('virtualBackground.blur') }
|
content = { t('virtualBackground.blur') }
|
||||||
position = { 'top' }>
|
position = { 'top' }>
|
||||||
<div
|
<div
|
||||||
aria-checked = { _selectedThumbnail === 'blur' }
|
aria-checked = { selectedThumbnail === 'blur' }
|
||||||
aria-label = { t('virtualBackground.blur') }
|
aria-label = { t('virtualBackground.blur') }
|
||||||
className = { cx('background-option', 'blur', {
|
className = { cx(classes.thumbnail, classes.blur,
|
||||||
'blur-selected': _selectedThumbnail === 'blur'
|
selectedThumbnail === 'blur' && classes.selectedThumbnail) }
|
||||||
}) }
|
|
||||||
onClick = { enableBlur }
|
onClick = { enableBlur }
|
||||||
onKeyPress = { enableBlurKeyPress }
|
onKeyPress = { enableBlurKeyPress }
|
||||||
role = 'radio'
|
role = 'radio'
|
||||||
|
@ -544,11 +438,11 @@ function VirtualBackground({
|
||||||
position = { 'top' }>
|
position = { 'top' }>
|
||||||
<img
|
<img
|
||||||
alt = { image.tooltip && t(`virtualBackground.${image.tooltip}`) }
|
alt = { image.tooltip && t(`virtualBackground.${image.tooltip}`) }
|
||||||
aria-checked = { options.selectedThumbnail === image.id
|
aria-checked = { options?.selectedThumbnail === image.id
|
||||||
|| _selectedThumbnail === image.id }
|
|| selectedThumbnail === image.id }
|
||||||
className = {
|
className = { cx(classes.thumbnail,
|
||||||
options.selectedThumbnail === image.id || _selectedThumbnail === image.id
|
(options?.selectedThumbnail === image.id
|
||||||
? 'background-option thumbnail-selected' : 'background-option thumbnail' }
|
|| selectedThumbnail === image.id) && classes.selectedThumbnail) }
|
||||||
data-imageid = { image.id }
|
data-imageid = { image.id }
|
||||||
onClick = { setImageBackground }
|
onClick = { setImageBackground }
|
||||||
onError = { onError }
|
onError = { onError }
|
||||||
|
@ -560,15 +454,13 @@ function VirtualBackground({
|
||||||
))}
|
))}
|
||||||
{storedImages.map((image, index) => (
|
{storedImages.map((image, index) => (
|
||||||
<div
|
<div
|
||||||
className = { 'thumbnail-container' }
|
className = { classes.storedImageContainer }
|
||||||
key = { image.id }>
|
key = { image.id }>
|
||||||
<img
|
<img
|
||||||
alt = { t('virtualBackground.uploadedImage', { index: index + 1 }) }
|
alt = { t('virtualBackground.uploadedImage', { index: index + 1 }) }
|
||||||
aria-checked = { _selectedThumbnail === image.id }
|
aria-checked = { selectedThumbnail === image.id }
|
||||||
className = { cx('background-option', {
|
className = { cx(classes.thumbnail,
|
||||||
'thumbnail-selected': _selectedThumbnail === image.id,
|
selectedThumbnail === image.id && classes.selectedThumbnail) }
|
||||||
'thumbnail': _selectedThumbnail !== image.id
|
|
||||||
}) }
|
|
||||||
data-imageid = { image.id }
|
data-imageid = { image.id }
|
||||||
onClick = { setUploadedImageBackground }
|
onClick = { setUploadedImageBackground }
|
||||||
onError = { onError }
|
onError = { onError }
|
||||||
|
@ -579,12 +471,12 @@ function VirtualBackground({
|
||||||
|
|
||||||
<Icon
|
<Icon
|
||||||
ariaLabel = { t('virtualBackground.deleteImage') }
|
ariaLabel = { t('virtualBackground.deleteImage') }
|
||||||
className = { 'delete-image-icon' }
|
className = { cx(classes.deleteImageIcon, 'delete-image-icon') }
|
||||||
data-imageid = { image.id }
|
data-imageid = { image.id }
|
||||||
onClick = { deleteStoredImage }
|
onClick = { deleteStoredImage }
|
||||||
onKeyPress = { deleteStoredImageKeyPress }
|
onKeyPress = { deleteStoredImageKeyPress }
|
||||||
role = 'button'
|
role = 'button'
|
||||||
size = { 15 }
|
size = { 16 }
|
||||||
src = { IconCloseLarge }
|
src = { IconCloseLarge }
|
||||||
tabIndex = { 0 } />
|
tabIndex = { 0 } />
|
||||||
</div>
|
</div>
|
||||||
|
@ -592,8 +484,30 @@ function VirtualBackground({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Dialog>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default VirtualBackgroundDialog;
|
/**
|
||||||
|
* 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) {
|
||||||
|
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'],
|
||||||
|
_showUploadButton: !state['features/base/config'].disableAddingBackgroundImages,
|
||||||
|
_multiStreamModeEnabled: getMultipleVideoSendingSupportFeatureFlag(state)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(_mapStateToProps)(translate(VirtualBackgrounds));
|
|
@ -1,2 +1 @@
|
||||||
export { default as VideoBackgroundButton } from './VideoBackgroundButton';
|
export { default as VideoBackgroundButton } from './VideoBackgroundButton';
|
||||||
export { default as VirtualBackgroundDialog } from './VirtualBackgroundDialog';
|
|
||||||
|
|
Loading…
Reference in New Issue