import React, { useState, useRef, useContext, useEffect } from 'react'; import { findDOMNode } from 'react-dom'; import { defineMessages, injectIntl } from 'react-intl'; import PropTypes from 'prop-types'; import _ from 'lodash'; import Styled from './styles'; import { EFFECT_TYPES, BLUR_FILENAME, IMAGE_NAMES, getVirtualBackgroundThumbnail, isVirtualBackgroundSupported, } from '/imports/ui/services/virtual-background/service'; import { CustomVirtualBackgroundsContext } from './context'; const propTypes = { intl: PropTypes.shape({ formatMessage: PropTypes.func.isRequired, }).isRequired, handleVirtualBgSelected: PropTypes.func.isRequired, locked: PropTypes.bool.isRequired, showThumbnails: PropTypes.bool, initialVirtualBgState: PropTypes.shape({ type: PropTypes.string.isRequired, name: PropTypes.string, }), }; const intlMessages = defineMessages({ virtualBackgroundSettingsLabel: { id: 'app.videoPreview.webcamVirtualBackgroundLabel', description: 'Label for the virtual background', }, virtualBackgroundSettingsDisabledLabel: { id: 'app.videoPreview.webcamVirtualBackgroundDisabledLabel', description: 'Label for the unsupported virtual background', }, noneLabel: { id: 'app.video.virtualBackground.none', description: 'Label for no virtual background selected', }, customLabel: { id: 'app.video.virtualBackground.custom', description: 'Label for custom virtual background selected', }, removeLabel: { id: 'app.video.virtualBackground.remove', description: 'Label for remove custom virtual background', }, blurLabel: { id: 'app.video.virtualBackground.blur', description: 'Label for the blurred camera option', }, camBgAriaDesc: { id: 'app.video.virtualBackground.camBgAriaDesc', description: 'Label for virtual background button aria', }, background: { id: 'app.video.virtualBackground.background', description: 'Label for the background word', }, ...IMAGE_NAMES.reduce((prev, imageName) => { const id = imageName.split('.').shift(); return { ...prev, [id]: { id: `app.video.virtualBackground.${id}`, description: `Label for the ${id} camera option`, defaultMessage: '{background} {index}', }, }; }, {}) }); const VirtualBgSelector = ({ intl, handleVirtualBgSelected, locked, showThumbnails, initialVirtualBgState, isVisualEffects, }) => { const [currentVirtualBg, setCurrentVirtualBg] = useState({ ...initialVirtualBgState, }); const inputElementsRef = useRef([]); const { dispatch, loaded, customBackgrounds, newCustomBackgrounds, loadFromDB, } = useContext(CustomVirtualBackgroundsContext); useEffect(() => { if (!loaded && isVisualEffects) loadFromDB(); }, []); const _virtualBgSelected = (type, name, index, customParams) => handleVirtualBgSelected(type, name, customParams) .then(switched => { // Reset to the base NONE_TYPE effect if it failed because the expected // behaviour from upstream's method is to actually stop/reset the effect // service if it fails if (!switched) { return setCurrentVirtualBg({ type: EFFECT_TYPES.NONE_TYPE }); } if (index >= 0) { findDOMNode(inputElementsRef.current[index]).focus(); } return setCurrentVirtualBg({ type, name }); }); const renderDropdownSelector = () => { const disabled = locked || !isVirtualBackgroundSupported(); return (
{ const { type, name } = JSON.parse(event.target.value); _virtualBgSelected(type, name); }} > {IMAGE_NAMES.map((imageName, i) => { const k = `${imageName}-${i}`; return ( ); })}
); } const customBgSelectorRef = useRef(null); const handleCustomBgChange = (event) => { const file = event.target.files[0]; const { name: filename } = file; const reader = new FileReader(); const substrings = filename.split('.'); substrings.pop(); const filenameWithoutExtension = substrings.join(''); reader.onload = function (e) { const background = { filename: filenameWithoutExtension, data: e.target.result, uniqueId: _.uniqueId(), }; dispatch({ type: 'new', background, }); } reader.readAsDataURL(file); } const renderThumbnailSelector = () => { const disabled = locked || !isVirtualBackgroundSupported(); return ( <> _virtualBgSelected(EFFECT_TYPES.NONE_TYPE)} isVisualEffects={isVisualEffects} />
{intl.formatMessage(intlMessages.camBgAriaDesc, { 0: EFFECT_TYPES.NONE_TYPE })}
<> { inputElementsRef.current[0] = ref; }} onClick={() => _virtualBgSelected(EFFECT_TYPES.BLUR_TYPE, 'Blur', 0)} isVisualEffects={isVisualEffects} />
{intl.formatMessage(intlMessages.camBgAriaDesc, { 0: EFFECT_TYPES.BLUR_TYPE })}
{IMAGE_NAMES.map((imageName, index) => { const label = intl.formatMessage(intlMessages[imageName.split('.').shift()], { index: index + 2, background: intl.formatMessage(intlMessages.background), }); return (
inputElementsRef.current[index + 1] = ref} onClick={() => _virtualBgSelected(EFFECT_TYPES.IMAGE_TYPE, imageName, index + 1)} disabled={disabled} isVisualEffects={isVisualEffects} /> { const node = findDOMNode(inputElementsRef.current[index + 1]); node.focus(); node.click(); }} aria-hidden src={getVirtualBackgroundThumbnail(imageName)} />
{intl.formatMessage(intlMessages.camBgAriaDesc, { 0: label })}
) })} {isVisualEffects && customBackgrounds .concat(newCustomBackgrounds) .map(({ filename, data, uniqueId }, index) => { const imageIndex = index + IMAGE_NAMES.length + 2; const label = `Background ${imageIndex}`; return ( inputElementsRef.current[index + IMAGE_NAMES.length + 1] = ref} onClick={() => { const node = findDOMNode(inputElementsRef.current[index + IMAGE_NAMES.length + 1]); node.focus(); node.click(); _virtualBgSelected( EFFECT_TYPES.IMAGE_TYPE, filename, imageIndex - 1, { file: data }, ); }} disabled={disabled} isVisualEffects={isVisualEffects} /> { dispatch({ type: 'delete', uniqueId, }); }} />
{label}
); })} {isVisualEffects && ( <> { if (customBgSelectorRef.current) { customBgSelectorRef.current.click(); } }} isVisualEffects={isVisualEffects} />
{intl.formatMessage(intlMessages.customLabel)}
)}
); }; const renderSelector = () => { if (showThumbnails) return renderThumbnailSelector(); return renderDropdownSelector(); }; return ( <> {!isVisualEffects && ( {!isVirtualBackgroundSupported() ? intl.formatMessage(intlMessages.virtualBackgroundSettingsDisabledLabel) : intl.formatMessage(intlMessages.virtualBackgroundSettingsLabel)} )} {renderSelector()} ); }; VirtualBgSelector.propTypes = propTypes; VirtualBgSelector.defaultProps = { showThumbnails: false, initialVirtualBgState: { type: EFFECT_TYPES.NONE_TYPE, }, }; export default injectIntl(VirtualBgSelector);