Merge pull request #20495 from germanocaumo/new-video-preview-design

feat(video-preview): new modal design
This commit is contained in:
Ramón Souza 2024-06-21 15:05:40 -03:00 committed by GitHub
commit 6581ed1f11
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 452 additions and 237 deletions

View File

@ -21,6 +21,7 @@ import {
} from '/imports/ui/services/virtual-background/service';
import { getSettingsSingletonInstance } from '/imports/ui/services/settings';
import Checkbox from '/imports/ui/components/common/checkbox/component'
import AppService from '/imports/ui/components/app/service';
const VIEW_STATES = {
finding: 'finding',
@ -48,9 +49,9 @@ const defaultProps = {
};
const intlMessages = defineMessages({
webcamEffectsTitle: {
id: 'app.videoPreview.webcamEffectsTitle',
description: 'Title for the video effects modal',
webcamVirtualBackgroundTitle: {
id: 'app.videoPreview.webcamVirtualBackgroundLabel',
description: 'Title for the virtual background modal',
},
webcamSettingsTitle: {
id: 'app.videoPreview.webcamSettingsTitle',
@ -60,6 +61,10 @@ const intlMessages = defineMessages({
id: 'app.videoPreview.closeLabel',
description: 'Close button label',
},
cancelLabel: {
id: 'app.mobileAppModal.dismissLabel',
description: 'Close button label',
},
webcamPreviewLabel: {
id: 'app.videoPreview.webcamPreviewLabel',
description: 'Webcam preview label',
@ -227,11 +232,13 @@ class VideoPreview extends Component {
this.handleVirtualBgSelected = this.handleVirtualBgSelected.bind(this);
this.handleLocalStreamInactive = this.handleLocalStreamInactive.bind(this);
this.handleBrightnessAreaChange = this.handleBrightnessAreaChange.bind(this);
this.handleSelectTab = this.handleSelectTab.bind(this);
this._isMounted = false;
this.state = {
webcamDeviceId,
selectedTab: 0,
availableWebcams: null,
selectedProfile: null,
isStartSharingDisabled: true,
@ -542,12 +549,12 @@ class VideoPreview extends Component {
}
handleProceed() {
const { resolve, closeModal, sharedDevices, isVisualEffects } = this.props;
const { resolve, closeModal, sharedDevices } = this.props;
const { webcamDeviceId, brightness } = this.state;
const shared = sharedDevices.includes(webcamDeviceId);
if (
(shared || isVisualEffects)
(shared)
&& this.currentVideoStream.virtualBgService
&& brightness === 100
&& this.currentVideoStream.virtualBgType === EFFECT_TYPES.NONE_TYPE
@ -740,9 +747,7 @@ class VideoPreview extends Component {
const {
intl,
sharedDevices,
isVisualEffects,
cameraAsContent,
isVirtualBackgroundsEnabled,
} = this.props;
const {
@ -751,120 +756,95 @@ class VideoPreview extends Component {
selectedProfile,
} = this.state;
const shared = sharedDevices.includes(webcamDeviceId);
const shouldShowVirtualBackgrounds = isVirtualBackgroundsEnabled && !cameraAsContent;
return (
<Styled.InternCol>
<Styled.Label htmlFor="setCam">
{intl.formatMessage(intlMessages.cameraLabel)}
</Styled.Label>
{ availableWebcams && availableWebcams.length > 0
? (
<Styled.Select
id="setCam"
value={webcamDeviceId || ''}
onChange={this.handleSelectWebcam}
>
{availableWebcams.map((webcam, index) => (
<option key={webcam.deviceId} value={webcam.deviceId}>
{webcam.label || this.getFallbackLabel(webcam, index)}
</option>
))}
</Styled.Select>
)
: (
<span>
{intl.formatMessage(intlMessages.webcamNotFoundLabel)}
</span>
)
}
{this.renderQualitySelector()}
</Styled.InternCol>
);
}
renderQualitySelector() {
const {
intl,
cameraAsContent,
} = this.props
const {
selectedProfile,
availableWebcams,
webcamDeviceId,
} = this.state;
const shared = this.isAlreadyShared(webcamDeviceId);
if (shared) {
return (
<Styled.Label>
{intl.formatMessage(intlMessages.sharedCameraLabel)}
</Styled.Label>
);
}
if (cameraAsContent) return;
const CAMERA_PROFILES = window.meetingClientSettings.public.kurento.cameraProfiles || [];
// Filtered, without hidden profiles
const PREVIEW_CAMERA_PROFILES = CAMERA_PROFILES.filter(p => !p.hidden);
if (isVisualEffects) {
return (
<>
{isVirtualBackgroundsEnabled && this.renderVirtualBgSelector()}
</>
);
}
return (
<>
<Styled.Label htmlFor="setQuality">
{intl.formatMessage(intlMessages.qualityLabel)}
</Styled.Label>
{PREVIEW_CAMERA_PROFILES.length > 0
? (
<Styled.Select
id="setQuality"
value={selectedProfile || ''}
onChange={this.handleSelectProfile}
>
{PREVIEW_CAMERA_PROFILES.map((profile) => {
const label = intlMessages[`${profile.id}`]
? intl.formatMessage(intlMessages[`${profile.id}`])
: profile.name;
{ cameraAsContent
? (
<>
<Styled.Label htmlFor="setCam">
{intl.formatMessage(intlMessages.cameraLabel)}
</Styled.Label>
{ availableWebcams && availableWebcams.length > 0
? (
<Styled.Select
id="setCam"
value={webcamDeviceId || ''}
onChange={this.handleSelectWebcam}
>
{availableWebcams.map((webcam, index) => (
<option key={webcam.deviceId} value={webcam.deviceId}>
{webcam.label || this.getFallbackLabel(webcam, index)}
</option>
))}
</Styled.Select>
)
: (
<span>
{intl.formatMessage(intlMessages.webcamNotFoundLabel)}
</span>
)
}
</>
)
:
<>
<Styled.Label htmlFor="setCam">
{intl.formatMessage(intlMessages.cameraLabel)}
</Styled.Label>
{ availableWebcams && availableWebcams.length > 0
? (
<Styled.Select
id="setCam"
value={webcamDeviceId || ''}
onChange={this.handleSelectWebcam}
>
{availableWebcams.map((webcam, index) => (
<option key={webcam.deviceId} value={webcam.deviceId}>
{webcam.label || this.getFallbackLabel(webcam, index)}
return (
<option key={profile.id} value={profile.id}>
{`${label}`}
</option>
))}
</Styled.Select>
)
: (
<span>
{intl.formatMessage(intlMessages.webcamNotFoundLabel)}
</span>
)
}
{ shared
? (
<Styled.Label>
{intl.formatMessage(intlMessages.sharedCameraLabel)}
</Styled.Label>
)
: (
<>
<Styled.Label htmlFor="setQuality">
{intl.formatMessage(intlMessages.qualityLabel)}
</Styled.Label>
{PREVIEW_CAMERA_PROFILES.length > 0
? (
<Styled.Select
id="setQuality"
value={selectedProfile || ''}
onChange={this.handleSelectProfile}
>
{PREVIEW_CAMERA_PROFILES.map((profile) => {
const label = intlMessages[`${profile.id}`]
? intl.formatMessage(intlMessages[`${profile.id}`])
: profile.name;
return (
<option key={profile.id} value={profile.id}>
{`${label}`}
</option>
);
})}
</Styled.Select>
)
: (
<span>
{intl.formatMessage(intlMessages.profileNotFoundLabel)}
</span>
)
}
</>
)
}
{shouldShowVirtualBackgrounds && this.renderVirtualBgSelector()}
</>
}
);
})}
</Styled.Select>
)
: (
<span>
{intl.formatMessage(intlMessages.profileNotFoundLabel)}
</span>
)
}
</>
);
}
@ -878,6 +858,7 @@ class VideoPreview extends Component {
renderBrightnessInput() {
const {
cameraAsContent,
cameraAsContentDeviceId,
} = this.props;
const {
webcamDeviceId,
@ -896,10 +877,10 @@ class VideoPreview extends Component {
? (brightness * 100) / 200
: ((200 - brightness) * 100) / 200;
if(cameraAsContent){ return null }
if(cameraAsContent || webcamDeviceId === cameraAsContentDeviceId){ return null }
return (
<>
<Styled.InternCol>
<Styled.Label htmlFor="brightness">
{intl.formatMessage(intlMessages.brightness)}
</Styled.Label>
@ -947,12 +928,12 @@ class VideoPreview extends Component {
label={intl.formatMessage(intlMessages.wholeImageBrightnessLabel)}
/>
</div>
</>
</Styled.InternCol>
);
}
renderVirtualBgSelector() {
const { isVisualEffects, isCustomVirtualBackgroundsEnabled } = this.props;
const { isCustomVirtualBackgroundsEnabled } = this.props;
const { isStartSharingDisabled, webcamDeviceId } = this.state;
const initialVirtualBgState = this.currentVideoStream ? {
type: this.currentVideoStream.virtualBgType,
@ -969,13 +950,37 @@ class VideoPreview extends Component {
locked={isStartSharingDisabled}
showThumbnails={SHOW_THUMBNAILS}
initialVirtualBgState={initialVirtualBgState}
isVisualEffects={isVisualEffects}
isCustomVirtualBackgroundsEnabled={isCustomVirtualBackgroundsEnabled}
/>
);
}
renderContent() {
renderTabsContent(tabNumber) {
const {
cameraAsContent,
isVirtualBackgroundsEnabled,
} = this.props;
const shouldShowVirtualBackgrounds = isVirtualBackgroundsEnabled && !cameraAsContent;
return (
<Styled.ContentCol>
{tabNumber === 0 && (
<Styled.Col>
{this.renderDeviceSelectors()}
{this.renderBrightnessInput()}
</Styled.Col>
)}
{tabNumber === 1 && shouldShowVirtualBackgrounds && (
<Styled.BgnCol>
{this.renderVirtualBgSelector()}
</Styled.BgnCol>
)}
</Styled.ContentCol>
);
}
renderContent(selectedTab) {
const {
intl,
} = this.props;
@ -989,12 +994,20 @@ class VideoPreview extends Component {
const Settings = getSettingsSingletonInstance();
const { animations } = Settings.application;
const containerStyle = {
width: '60%',
height: '25vh',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
};
switch (viewState) {
case VIEW_STATES.finding:
return (
<Styled.Content>
<Styled.VideoCol>
<div>
<div style={containerStyle}>
<span>{intl.formatMessage(intlMessages.findingWebcamsLabel)}</span>
<Styled.FetchingAnimation animations={animations} />
</div>
@ -1030,10 +1043,7 @@ class VideoPreview extends Component {
)
}
</Styled.VideoCol>
<Styled.Col>
{this.renderDeviceSelectors()}
{this.renderBrightnessInput()}
</Styled.Col>
{this.renderTabsContent(selectedTab)}
</Styled.Content>
);
}
@ -1045,13 +1055,13 @@ class VideoPreview extends Component {
return intl.formatMessage(intlMessages.webcamSettingsTitle);
}
renderModalContent() {
renderModalContent(selectedTab) {
const {
intl,
hasVideoStream,
forceOpen,
camCapReached,
isVisualEffects,
closeModal,
} = this.props;
const {
@ -1066,6 +1076,8 @@ class VideoPreview extends Component {
const shared = this.isAlreadyShared(webcamDeviceId);
const showStopAllButton = hasVideoStream && VideoService.isMultipleCamerasEnabled();
const { isIe } = browserInfo;
return (
@ -1083,50 +1095,72 @@ class VideoPreview extends Component {
</Styled.BrowserWarning>
) : null}
{this.renderContent()}
{this.renderContent(selectedTab)}
{!isVisualEffects ? (
<Styled.Footer>
{hasVideoStream && VideoService.isMultipleCamerasEnabled()
? (
<Styled.Footer>
<Styled.BottomSeparator />
<Styled.FooterContainer>
{showStopAllButton ? (
<Styled.ExtraActions>
<Button
<Styled.StopAllButton
color="danger"
label={intl.formatMessage(intlMessages.stopSharingAllLabel)}
onClick={this.handleStopSharingAll}
disabled={shouldDisableButtons}
/>
</Styled.ExtraActions>
)
: null
}
<Styled.Actions>
{!shared && camCapReached ? (
<span>{intl.formatMessage(intlMessages.camCapReached)}</span>
) : (<Button
data-test="startSharingWebcam"
color={shared ? 'danger' : 'primary'}
label={intl.formatMessage(shared ? intlMessages.stopSharingLabel : intlMessages.startSharingLabel)}
onClick={shared ? this.handleStopSharing : this.handleStartSharing}
disabled={isStartSharingDisabled || isStartSharingDisabled === null || shouldDisableButtons}
/>)}
</Styled.Actions>
</Styled.Footer>
) : null }
) : null}
{!shared && camCapReached ? (
<span>{intl.formatMessage(intlMessages.camCapReached)}</span>
) : (
<div style={{ display: 'flex' }}>
<Styled.CancelButton
data-test="cancelSharingWebcam"
label={intl.formatMessage(intlMessages.cancelLabel)}
onClick={closeModal}
/>
<Styled.SharingButton
data-test="startSharingWebcam"
color={shared ? 'danger' : 'primary'}
label={intl.formatMessage(shared ? intlMessages.stopSharingLabel : intlMessages.startSharingLabel)}
onClick={shared ? this.handleStopSharing : this.handleStartSharing}
disabled={isStartSharingDisabled || isStartSharingDisabled === null || shouldDisableButtons}
/>
</div>
)}
</Styled.FooterContainer>
</Styled.Footer>
</>
);
}
handleSelectTab(tab) {
this.setState({
selectedTab: tab,
});
}
render() {
const {
intl,
isCamLocked,
forceOpen,
isVisualEffects,
isOpen,
priority,
cameraAsContent,
cameraAsContentDeviceId,
isVirtualBackgroundsEnabled,
} = this.props;
const { selectedTab, webcamDeviceId } = this.state;
const BASE_NAME = window.meetingClientSettings.public.app.basename;
const WebcamSettingsImg = `${BASE_NAME}/resources/images/webcam_settings.svg`;
const WebcamBackgroundImg = `${BASE_NAME}/resources/images/webcam_background.svg`;
const darkThemeState = AppService.isDarkThemeEnabled();
if (isCamLocked === true) {
this.handleProceed();
return null;
@ -1145,9 +1179,9 @@ class VideoPreview extends Component {
|| !PreviewService.getSkipVideoPreview()
|| forceOpen;
const title = isVisualEffects
? intl.formatMessage(intlMessages.webcamEffectsTitle)
: intl.formatMessage(intlMessages.webcamSettingsTitle);
const shouldShowVirtualBackgroundsTab = isVirtualBackgroundsEnabled
&& !cameraAsContent
&& !(webcamDeviceId === cameraAsContentDeviceId)
return (
<Styled.VideoPreviewModal
@ -1157,16 +1191,50 @@ class VideoPreview extends Component {
shouldCloseOnOverlayClick={allowCloseModal}
isPhone={deviceInfo.isPhone}
data-test="webcamSettingsModal"
title={title}
{...{
isOpen,
priority,
}}
>
{deviceInfo.hasMediaDevices
? this.renderModalContent()
: this.supportWarning()
}
<Styled.Container>
<Styled.Header>
<Styled.WebcamTabs
onSelect={this.handleSelectTab}
selectedIndex={selectedTab}
>
<Styled.WebcamTabList>
<Styled.WebcamTabSelector selectedClassName="is-selected">
<Styled.IconSvg
src={WebcamSettingsImg}
darkThemeState={darkThemeState}
/>
<span
id="webcam-settings-title">{this.getModalTitle()}
</span>
</Styled.WebcamTabSelector>
{shouldShowVirtualBackgroundsTab && (
<>
<Styled.HeaderSeparator />
<Styled.WebcamTabSelector selectedClassName="is-selected">
<Styled.IconSvg
src={WebcamBackgroundImg}
darkThemeState={darkThemeState}
/>
<span id="backgrounds-title">{intl.formatMessage(intlMessages.webcamVirtualBackgroundTitle)}</span>
</Styled.WebcamTabSelector>
</>
)}
</Styled.WebcamTabList>
</Styled.WebcamTabs>
</Styled.Header>
{deviceInfo.hasMediaDevices
? this.renderModalContent(selectedTab)
: this.supportWarning()
}
</Styled.Container>
</Styled.VideoPreviewModal>
);
}

View File

@ -30,7 +30,8 @@ const VideoPreviewContainer = (props) => {
const hasVideoStream = useHasVideoStream();
const camCapReached = useHasCapReached();
const isCamLocked = useIsUserLocked();
const webcamDeviceId = useStorageKey('WebcamDeviceId');
const settingsStorage = window.meetingClientSettings.public.app.userSettingsStorage;
const webcamDeviceId = useStorageKey('WebcamDeviceId', settingsStorage);
const isVirtualBackgroundsEnabled = useIsVirtualBackgroundsEnabled();
const isCustomVirtualBackgroundsEnabled = useIsCustomVirtualBackgroundsEnabled();
const isCameraAsContentBroadcasting = ScreenShareService.useIsCameraAsContentBroadcasting();

View File

@ -1,12 +1,18 @@
import styled, { css, keyframes } from 'styled-components';
import {
borderSizeSmall,
borderSize,
borderSizeLarge,
mdPaddingX,
titlePositionLeft,
lgPaddingY,
} from '/imports/ui/stylesheets/styled-components/general';
import {
colorGrayLabel,
colorWhite,
colorBlack,
colorGrayLighter,
colorGrayLightest,
colorPrimary,
colorText,
} from '/imports/ui/stylesheets/styled-components/palette';
@ -14,10 +20,16 @@ import {
fontSizeLarge,
lineHeightComputed,
headingsFontWeight,
fontSizeLarger,
fontSizeSmall,
} from '/imports/ui/stylesheets/styled-components/typography';
import { smallOnly, landscape } from '/imports/ui/stylesheets/styled-components/breakpoints';
import { smallOnly, mediumOnly, landscape } from '/imports/ui/stylesheets/styled-components/breakpoints';
import ModalSimple from '/imports/ui/components/common/modal/simple/component';
import ModalStyles from '/imports/ui/components/common/modal/simple/styles';
import Button from '/imports/ui/components/common/button/component';
import {
Tab, Tabs, TabList,
} from 'react-tabs';
const Warning = styled.div`
text-align: center;
@ -44,18 +56,59 @@ const Col = styled.div`
justify-content: center;
margin: 0 0.5rem 0 0.5rem;
width: 50%;
@media ${smallOnly}, ${mediumOnly} {
justify-content: space-between;
align-items: center;
overflow: auto;
margin: 0;
}
`;
const BgnCol = styled.div`
display: flex;
flex-direction: column;
height: 100%;
justify-content: center;
margin: 0 0.5rem 0 0.5rem;
@media ${smallOnly} {
justify-content: space-between;
align-items: center;
margin: 0;
}
`;
const InternCol = styled.div`
display: flex;
flex-direction: column;
height: 100%;
justify-content: center;
margin: 0 0.5rem 0 0.5rem;
@media ${smallOnly} {
width: 90%;
height: unset;
}
`;
const ContentCol = styled.div`
width: 60%;
height: 25vh;
@media ${smallOnly} {
width: 90%;
}
`;
const BackgroundCol = styled.div`
display: flex;
flex-direction: column;
`;
const VideoCol = styled(Col)`
align-items: center;
@media ${landscape} {
width: 33.3%;
width: 50%;
}
`;
@ -94,14 +147,15 @@ const Content = styled.div`
display: flex;
justify-content: center;
align-items: center;
overflow: auto;
color: ${colorText};
font-weight: normal;
padding: ${lineHeightComputed} 0;
@media ${smallOnly} {
flex-direction: column;
margin: 0;
display: flex;
flex-wrap: wrap;
flex-direction: row;
margin: 5px;
}
`;
@ -116,11 +170,23 @@ const BrowserWarning = styled.p`
const Footer = styled.div`
display: flex;
flex-direction: column;
`;
const FooterContainer = styled.div`
display: flex;
align-items: center;
justify-content: ${({ showStopAllButton }) => (showStopAllButton ? 'flex-start' : 'flex-end')};
@media ${smallOnly} {
display: flex;
flex-direction: column;
}
`;
const Actions = styled.div`
margin-left: auto;
margin-right: auto;
display: flex;
justify-content: flex-end;
[dir="rtl"] & {
margin-right: auto;
@ -167,7 +233,6 @@ const VideoPreviewModal = styled(ModalSimple)`
@media ${smallOnly} {
height: unset;
min-height: 22.5rem;
max-height: unset;
}
${({ isPhone }) => isPhone && `
@ -244,17 +309,120 @@ const MarkerDynamicWrapper = styled.div`
user-select: none;
`;
const Container = styled.div`
padding: 0 calc(${mdPaddingX} / 2 + ${borderSize});
`;
const Header = styled.div`
margin: 0;
padding: 0;
border: none;
line-height: ${titlePositionLeft};
margin-bottom: ${lgPaddingY};
`;
const WebcamTabs = styled(Tabs)`
display: flex;
flex-flow: column;
&:hover {
cursor: pointer;
}
`;
const WebcamTabList = styled(TabList)`
display: flex;
justify-content: space-around;
padding: 0;
list-style-type: none;
`;
const WebcamTabSelector = styled(Tab)`
display: flex;
justify-content: center;
flex-grow: 1;
text-align: center;
font-weight: bold;
color: ${colorGrayLighter};
&.is-selected {
border: none;
color: ${colorBlack};
}
`;
const HeaderSeparator = styled.div`
border-left: 1px solid ${colorText};
content: '|';
margin: 0 1.5rem;
height: 1.5rem;
align-self: center;
opacity: 0.75;
`;
const BottomSeparator = styled.div`
position: relative;
width: 100%;
height: ${borderSizeSmall};
background-color: ${colorGrayLightest};
margin-top: calc(${lineHeightComputed} * 1.25);
margin-bottom: calc(${lineHeightComputed} * 1.25);
`;
const IconSvg = styled.img`
height: ${fontSizeLarger};
border-radius: 5px;
margin: 5px;
${({ darkThemeState }) => darkThemeState && css`
filter: invert(1);
`}
`;
const SharingButton = styled(Button)`
margin: 0 0.5rem;
height: 2.5rem;
`;
const CancelButton = styled(Button)`
margin: 0 0.5rem;
height: 2.5rem;
`;
const StopAllButton = styled(Button)`
margin: 0 0.5rem;
height: 2.5rem;
`;
export default {
Warning,
Main,
Text,
Col,
BgnCol,
ContentCol,
InternCol,
Container,
Header,
WebcamTabs,
WebcamTabList,
WebcamTabSelector,
HeaderSeparator,
BottomSeparator,
VideoCol,
BackgroundCol,
IconSvg,
SharingButton,
CancelButton,
StopAllButton,
Label,
Select,
Content,
BrowserWarning,
Footer,
FooterContainer,
Actions,
ExtraActions,
VideoPreviewModal,

View File

@ -51,7 +51,6 @@ const VirtualBgSelector = ({
locked,
showThumbnails = false,
initialVirtualBgState = defaultInitialVirtualBgState,
isVisualEffects,
readFile,
isCustomVirtualBackgroundsEnabled,
}) => {
@ -269,7 +268,6 @@ const VirtualBgSelector = ({
return (
<Styled.ThumbnailButtonWrapper
key={`blur-${index}`}
isVisualEffects={isVisualEffects}
>
<Styled.ThumbnailButton
background={getVirtualBackgroundThumbnail(BLUR_FILENAME)}
@ -282,7 +280,6 @@ const VirtualBgSelector = ({
disabled={disabled}
ref={ref => { inputElementsRef.current[index] = ref; }}
onClick={() => _virtualBgSelected(EFFECT_TYPES.BLUR_TYPE, 'Blur', index)}
isVisualEffects={isVisualEffects}
/>
<div aria-hidden className="sr-only" id={`vr-cam-btn-blur`}>
{intl.formatMessage(intlMessages.camBgAriaDesc, { 0: EFFECT_TYPES.BLUR_TYPE })}
@ -300,7 +297,6 @@ const VirtualBgSelector = ({
return (
<Styled.ThumbnailButtonWrapper
key={`${imageName}-${index + 1}`}
isVisualEffects={isVisualEffects}
>
<Styled.ThumbnailButton
id={`${imageName}-${index + 1}`}
@ -314,7 +310,6 @@ const VirtualBgSelector = ({
ref={ref => inputElementsRef.current[index] = ref}
onClick={() => _virtualBgSelected(EFFECT_TYPES.IMAGE_TYPE, imageName, index)}
disabled={disabled}
isVisualEffects={isVisualEffects}
background={getVirtualBackgroundThumbnail(imageName)}
data-test="selectDefaultBackground"
/>
@ -334,7 +329,6 @@ const VirtualBgSelector = ({
return (
<Styled.ThumbnailButtonWrapper
key={`${filename}-${index + 1}`}
isVisualEffects={isVisualEffects}
>
<Styled.ThumbnailButton
id={`${filename}-${index + 1}`}
@ -353,7 +347,6 @@ const VirtualBgSelector = ({
{ file: data, uniqueId },
)}
disabled={disabled}
isVisualEffects={isVisualEffects}
background={data}
data-test="selectCustomBackground"
/>
@ -399,7 +392,6 @@ const VirtualBgSelector = ({
customBgSelectorRef.current.click();
}
}}
isVisualEffects={isVisualEffects}
data-test="inputBackgroundButton"
/>
<input
@ -427,7 +419,6 @@ const VirtualBgSelector = ({
tabIndex={disabled ? -1 : 0}
disabled={disabled}
onClick={() => _virtualBgSelected(EFFECT_TYPES.NONE_TYPE)}
isVisualEffects={isVisualEffects}
data-test="noneBackgroundButton"
/>
<div aria-hidden className="sr-only" id={`vr-cam-btn-none`}>
@ -454,7 +445,6 @@ const VirtualBgSelector = ({
<Styled.BgWrapper
role="group"
aria-label={intl.formatMessage(intlMessages.virtualBackgroundSettingsLabel)}
isVisualEffects={isVisualEffects}
brightnessEnabled={ENABLE_CAMERA_BRIGHTNESS}
data-test="virtualBackground"
>
@ -507,13 +497,13 @@ const VirtualBgSelector = ({
return (
<>
{!isVisualEffects && (
<Styled.Label>
{!isVirtualBackgroundSupported()
? intl.formatMessage(intlMessages.virtualBackgroundSettingsDisabledLabel)
: intl.formatMessage(intlMessages.virtualBackgroundSettingsLabel)}
</Styled.Label>
)}
<Styled.Label>
{intl.formatMessage(
isVirtualBackgroundSupported()
? intlMessages.virtualBackgroundSettingsLabel
: intlMessages.virtualBackgroundSettingsDisabledLabel,
)}
</Styled.Label>
{renderSelector()}
</>

View File

@ -16,28 +16,27 @@ import {
} from '/imports/ui/stylesheets/styled-components/palette';
import { ScrollboxVertical } from '/imports/ui/stylesheets/styled-components/scrollable';
import { fontSizeSmallest } from '/imports/ui/stylesheets/styled-components/typography';
import { smallOnly, mediumOnly } from '/imports/ui/stylesheets/styled-components/breakpoints';
import Button from '/imports/ui/components/common/button/component';
const VirtualBackgroundRowThumbnail = styled.div`
margin-top: 0.4rem;
margin: 0.4rem;
`;
const BgWrapper = styled(ScrollboxVertical)`
display: flex;
justify-content: flex-start;
overflow-x: auto;
max-width: 272px;
max-height: 216px;
flex-wrap: wrap;
overflow-y: auto;
margin: ${borderSizeLarge};
padding: ${borderSizeLarge};
${({ isVisualEffects }) => isVisualEffects && `
height: 15rem;
flex-wrap: wrap;
align-content: flex-start;
`}
${({ brightnessEnabled, isVisualEffects }) => brightnessEnabled && isVisualEffects && `
height: 10rem;
`}
@media ${smallOnly}, ${mediumOnly} {
justify-content: center;
max-height: 22vh;
}
`;
const BgNoneButton = styled(Button)`
@ -45,12 +44,8 @@ const BgNoneButton = styled(Button)`
height: 48px;
width: 48px;
border: ${borderSizeSmall} solid ${userThumbnailBorder};
margin: 0 0.15rem;
margin: 0.5rem 0.5rem;
flex-shrink: 0;
${({ isVisualEffects }) => isVisualEffects && `
margin: 0.15rem;
`}
`;
const ThumbnailButton = styled(Button)`
@ -131,11 +126,7 @@ const Label = styled.label`
const ThumbnailButtonWrapper = styled.div`
position: relative;
margin: 0 0.15rem;
${({ isVisualEffects }) => isVisualEffects && `
margin: 0.15rem;
`}
margin: 0.5rem 0.5rem;
`;
const ButtonWrapper = styled.div`
@ -177,4 +168,4 @@ export default {
ButtonRemove,
BgCustomButton,
SkeletonWrapper,
};
};

View File

@ -5,7 +5,6 @@ import { IntlShape, defineMessages, injectIntl } from 'react-intl';
import deviceInfo from '/imports/utils/deviceInfo';
import { debounce } from '/imports/utils/debounce';
import BBBMenu from '/imports/ui/components/common/menu/component';
import { useIsVirtualBackgroundsEnabled } from '/imports/ui/services/features';
import Button from '/imports/ui/components/common/button/component';
import VideoPreviewContainer from '/imports/ui/components/video-preview/container';
import { CameraSettingsDropdownItemType } from 'bigbluebutton-html-plugin-sdk/dist/cjs/extensible-areas/camera-settings-dropdown-item/enums';
@ -87,15 +86,9 @@ const JoinVideoButton: React.FC<JoinVideoButtonProps> = ({
const isDesktopSharingCamera = hasVideoStream && !isMobile;
const ENABLE_WEBCAM_SELECTOR_BUTTON = window.meetingClientSettings.public.app.enableWebcamSelectorButton;
const ENABLE_CAMERA_BRIGHTNESS = window.meetingClientSettings.public.app.enableCameraBrightness;
const shouldEnableWebcamSelectorButton = ENABLE_WEBCAM_SELECTOR_BUTTON
&& isDesktopSharingCamera;
const isVirtualBackgroundsEnabled = useIsVirtualBackgroundsEnabled();
const shouldEnableWebcamVisualEffectsButton = (isVirtualBackgroundsEnabled
|| ENABLE_CAMERA_BRIGHTNESS)
&& hasVideoStream
&& !isMobile;
const exitVideo = () => isDesktopSharingCamera && (!VideoService.isMultipleCamerasEnabled()
|| shouldEnableWebcamSelectorButton);
@ -168,16 +161,6 @@ const JoinVideoButton: React.FC<JoinVideoButtonProps> = ({
);
}
if (shouldEnableWebcamVisualEffectsButton) {
actions.push(
{
key: 'virtualBgSelection',
label: intl.formatMessage(intlMessages.visualEffects),
onClick: () => handleOpenAdvancedOptions(() => setPropsToPassModal({ isVisualEffects: true })),
},
);
}
if (actions.length === 0) return null;
const customStyles = { top: '-3.6rem' };

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M120-574v-85l181-181h85L120-574Zm0-196v-70h70l-70 70Zm527 67q-10-11-21.5-21.5T602-743l97-97h85L647-703ZM220-361l77-77q7 11 14.5 20t16.5 17q-28 7-56.5 17.5T220-361Zm480-197v-2q0-19-3-37t-9-35l152-152v86L700-558ZM436-776l65-64h85l-64 64q-11-2-21-3t-21-1q-11 0-22 1t-22 3ZM120-375v-85l144-144q-2 11-3 22t-1 22q0 11 1 21t3 20L120-375Zm709 83q-8-12-18.5-23T788-335l52-52v85l-11 10Zm-116-82q-7-3-14-5.5t-14-4.5q-9-3-17.5-6t-17.5-5l190-191v86L713-374Zm-233-26q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 66-47 113t-113 47ZM160-120v-71q0-34 17-63t47-44q51-26 115.5-44T480-360q76 0 140.5 18T736-298q30 15 47 44t17 63v71H160Z"/></svg>

After

Width:  |  Height:  |  Size: 738 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h480q33 0 56.5 23.5T720-720v180l126-126q10-10 22-5t12 19v344q0 14-12 19t-22-5L720-420v180q0 33-23.5 56.5T640-160H160Z"/></svg>

After

Width:  |  Height:  |  Size: 282 B

View File

@ -429,7 +429,9 @@ exports.advancedVideoSettingsBtn = 'li[data-test="advancedVideoSettingsButton"]'
exports.mirrorWebcamBtn = 'li[data-test="mirrorWebcamBtn"]';
exports.focusWebcamBtn = 'li[data-test="focusWebcamBtn"]';
exports.pinWebcamBtn = 'li[data-test="pinWebcamBtn"]';
exports.webcamFullscreenButton = 'button[data-test="webcamFullscreenButton"]';
exports.webcamsFullscreenButton = 'li[data-test="webcamsFullscreenButton"]';
exports.webcamSettingsTitle = 'span[id="webcam-settings-title"]';
exports.backgroundSettingsTitle = 'span[id="backgrounds-title"]';
exports.selectDefaultBackground = 'button[data-test="selectDefaultBackground"]';
exports.selectCustomBackground = 'button[data-test="selectCustomBackground"]';
exports.removeCustomBackground = 'button[data-test="removeCustomBackground"]';

View File

@ -18,7 +18,7 @@ class DisabledFeatures extends MultiUsers {
await this.modPage.waitForSelector(e.audioModal, ELEMENT_WAIT_LONGER_TIME);
if(speechRecognitionEnabled) {
if (speechRecognitionEnabled) {
await this.modPage.wasRemoved(e.speechRecognition);
} else {
await this.modPage.wasRemoved(e.speechRecognitionUnsupported);
@ -64,7 +64,7 @@ class DisabledFeatures extends MultiUsers {
async virtualBackgrounds() {
await this.modPage.waitAndClick(e.joinVideo);
await this.modPage.wasRemoved(e.virtualBackgrounds);
await this.modPage.wasRemoved(e.backgroundSettingsTitle);
}
async downloadPresentationWithAnnotations() {
@ -93,8 +93,8 @@ class DisabledFeatures extends MultiUsers {
}
async customVirtualBackground() {
await this.modPage.waitAndClick (e.joinVideo);
await this.modPage.waitForSelector(e.webcamSettingsModal);
await this.modPage.waitAndClick(e.joinVideo);
await this.modPage.waitAndClick(e.backgroundSettingsTitle);
await this.modPage.wasRemoved(e.inputBackgroundButton);
}
@ -123,7 +123,7 @@ class DisabledFeatures extends MultiUsers {
await this.modPage.waitForSelector(e.audioModal, ELEMENT_WAIT_LONGER_TIME);
if(speechRecognitionEnabled) {
if (speechRecognitionEnabled) {
await this.modPage.wasRemoved(e.speechRecognition);
} else {
await this.modPage.wasRemoved(e.speechRecognitionUnsupported);
@ -169,6 +169,7 @@ class DisabledFeatures extends MultiUsers {
async virtualBackgroundsExclude() {
await this.modPage.waitAndClick(e.joinVideo);
await this.modPage.waitAndClick(e.backgroundSettingsTitle);
await this.modPage.hasElement(e.virtualBackgrounds);
}
@ -199,8 +200,8 @@ class DisabledFeatures extends MultiUsers {
}
async customVirtualBackgroundExclude() {
await this.modPage.waitAndClick (e.joinVideo);
await this.modPage.waitForSelector(e.webcamSettingsModal);
await this.modPage.waitAndClick(e.joinVideo);
await this.modPage.waitAndClick(e.backgroundSettingsTitle);
await this.modPage.hasElement(e.inputBackgroundButton);
}

View File

@ -63,6 +63,7 @@ class Webcam extends Page {
async applyBackground() {
await this.waitAndClick(e.joinVideo);
await this.waitAndClick(e.backgroundSettingsTitle);
await this.waitForSelector(e.noneBackgroundButton);
await this.waitAndClick(`${e.selectDefaultBackground}[aria-label="Home"]`);
await sleep(1000);
@ -75,11 +76,14 @@ class Webcam extends Page {
async webcamFullscreen() {
await this.shareWebcam();
// get default viewport sizes
const { windowWidth, windowHeight } = await this.page.evaluate(() => { return {
windowWidth: window.innerWidth,
windowHeight: window.innerHeight,
}});
await this.waitAndClick(e.webcamFullscreenButton);
const { windowWidth, windowHeight } = await this.page.evaluate(() => {
return {
windowWidth: window.innerWidth,
windowHeight: window.innerHeight,
}
});
await this.waitAndClick(e.dropdownWebcamButton);
await this.waitAndClick(e.webcamsFullscreenButton);
// get fullscreen webcam size
const { width, height } = await this.getLocator('video').boundingBox();
await expect(width + 1).toBe(windowWidth); // not sure why there is a difference of 1 pixel
@ -88,6 +92,7 @@ class Webcam extends Page {
async disableSelfView() {
await this.waitAndClick(e.joinVideo);
await this.waitAndClick(e.backgroundSettingsTitle);
await this.waitForSelector(e.noneBackgroundButton);
await uploadBackgroundVideoImage(this);
@ -105,6 +110,7 @@ class Webcam extends Page {
async managingNewBackground() {
await this.waitAndClick(e.joinVideo);
await this.waitAndClick(e.backgroundSettingsTitle);
await this.waitForSelector(e.noneBackgroundButton);
// Upload
@ -121,12 +127,14 @@ class Webcam extends Page {
// Remove
await this.waitAndClick(e.videoDropdownMenu);
await this.waitAndClick(e.advancedVideoSettingsBtn);
await this.waitAndClick(e.backgroundSettingsTitle);
await this.waitAndClick(e.removeCustomBackground);
await this.wasRemoved(e.selectCustomBackground);
}
async keepBackgroundWhenRejoin(context) {
await this.waitAndClick(e.joinVideo);
await this.waitAndClick(e.backgroundSettingsTitle);
await this.waitForSelector(e.noneBackgroundButton);
await uploadBackgroundVideoImage(this);
// Create a new page before closing the previous to not close the current context
@ -135,6 +143,7 @@ class Webcam extends Page {
const openedPage = await this.getLastTargetPage(context);
await openedPage.init(true, true, { meetingId: this.meetingId });
await openedPage.waitAndClick(e.joinVideo);
await openedPage.waitAndClick(e.backgroundSettingsTitle);
await openedPage.hasElement(e.selectCustomBackground);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB