import { OmitFunctions } from '@ringcentral-integration/utils/src/typeFunctions/OmitFunctions'; import { usePrevious } from '@ringcentral/juno'; import clsx from 'clsx'; // @ts-expect-error TS(7016): Could not find a declaration file for module 'rc-tooltip' import TooltipBase from 'rc-tooltip'; import React, { FC, useCallback, useEffect, useMemo, useRef, useState, } from 'react'; import InfoIcon from '../../assets/images/Info.svg'; import { PageHeader, PageHeaderBack, PageHeaderRemain, PageHeaderTitle, } from '../BackHeader/PageHeader'; import { Button } from '../Button'; import { DropdownSelect } from '../DropdownSelect'; import IconLine from '../IconLine'; import InputField from '../InputField'; import Panel from '../Panel'; import { SaveButton } from '../SaveButton'; import { AudioSettingsPanelProps } from './AudioSettingsPanel.interface'; import { VolumeSlider } from './VolumeSlider'; import i18n from './i18n'; import styles from './styles.scss'; // TODO: find a better tooltip solution? const Tooltip = typeof TooltipBase === 'function' ? TooltipBase : TooltipBase.default; const CheckMicPermission: FC< Pick< AudioSettingsPanelProps, 'checkUserMedia' | 'userMedia' | 'currentLocale' > > = ({ checkUserMedia, userMedia, currentLocale }) => { if (userMedia) { return null; } return ( {i18n.getString('checkMicPermission', currentLocale)} } > {i18n.getString('micNoPermissionMessage', currentLocale)} ); }; export const getFallbackLabel = ( devices: OmitFunctions[], index: number, currentLocale: string, ) => { let fallbackLabel = i18n.getString('noLabel', currentLocale); if (devices.length > 1) { fallbackLabel = `${fallbackLabel} ${index + 1}`; } return fallbackLabel; }; export const getDeviceValueRenderer = (devices: OmitFunctions[], currentLocale: string) => (value: string | null) => { if (value === null) { return i18n.getString('noDevice', currentLocale); } const index = devices.findIndex((device) => device.deviceId === value); if (index > -1 && devices[index].label) { return devices[index].label; } return getFallbackLabel(devices, index, currentLocale); }; export const getDeviceOptionRenderer = (devices: OmitFunctions[], currentLocale: string) => (device: OmitFunctions, index: number) => { if (device && device.label) { return device.label; } return getFallbackLabel(devices, index, currentLocale); }; const useDeviceRenderers = ( devices: OmitFunctions[], currentLocale: string, ) => { return useMemo( () => [ getDeviceValueRenderer(devices, currentLocale), getDeviceOptionRenderer(devices, currentLocale), ] as const, [devices, currentLocale], ); }; const deviceValueFunction = (device: OmitFunctions) => device.deviceId; const OutputDevice: FC< Pick< AudioSettingsPanelProps, | 'availableOutputDevices' | 'currentLocale' | 'outputDeviceDisabled' | 'outputDeviceId' > & { isFirefox: boolean; onChange: (device: OmitFunctions) => void; } > = ({ availableOutputDevices, currentLocale, isFirefox, onChange, outputDeviceDisabled, outputDeviceId, }) => { const [deviceValueRenderer, deviceOptionRenderer] = useDeviceRenderers( availableOutputDevices, currentLocale, ); if (isFirefox && !availableOutputDevices.length) { return ( {i18n.getString('outputDevice', currentLocale)}} noBorder >
{i18n.getString('defaultOutputDevice', currentLocale)}
); } return ( {i18n.getString('outputDevice', currentLocale)}} noBorder > ); }; const InputDevice: FC< Pick< AudioSettingsPanelProps, | 'availableInputDevices' | 'inputDeviceId' | 'inputDeviceDisabled' | 'currentLocale' > & { onChange: (device: OmitFunctions) => void; isFirefox: boolean; } > = ({ availableInputDevices, currentLocale, inputDeviceDisabled, inputDeviceId, isFirefox, onChange, }) => { const [deviceValueRenderer, deviceOptionRenderer] = useDeviceRenderers( availableInputDevices, currentLocale, ); const showTooltip = availableInputDevices.length > 0 ? availableInputDevices[0].label === '' : isFirefox; const tooltipContainer = useRef(null); const inputTooltip = showTooltip ? ( } getTooltipContainer={() => tooltipContainer.current} > ) : null; return ( {i18n.getString('inputDevice', currentLocale)} {inputTooltip} } noBorder >
{ tooltipContainer.current = el; }} /> ); }; function useDeviceIdState( deviceId: string, devices: OmitFunctions[], ) { const [deviceIdState, setDeviceIdState] = useState(deviceId); const setDeviceState = useCallback( (device: OmitFunctions) => { setDeviceIdState(device.deviceId); }, [setDeviceIdState], ); const oldDeviceId = usePrevious(() => deviceId, true); const oldDevices = usePrevious(() => devices, true); useEffect(() => { if (deviceId !== oldDeviceId) { setDeviceIdState(deviceId); } if (devices !== oldDevices) { if (!devices.find((device) => device.deviceId === deviceIdState)) { setDeviceIdState(deviceId); } } }, [oldDeviceId, oldDevices, devices, deviceIdState, deviceId]); return [deviceIdState, setDeviceState] as const; } const VolumeInput: FC<{ volume: number; minVolume?: number; maxVolume?: number; onChange: (volume: number) => void; label: string; }> = ({ volume, minVolume, maxVolume, onChange, label }) => { return ( {label}} noBorder> ); }; export const AudioSettingsPanel: FC = ({ availableInputDevices, availableOutputDevices, callVolume, checkUserMedia, className = null, currentLocale, inputDeviceDisabled = false, inputDeviceId, onBackButtonClick, onSave, outputDeviceDisabled = false, outputDeviceId, ringtoneVolume, showCallVolume = false, showRingToneVolume = false, supportDevices, userMedia, }) => { // For firefox, when input device have empty label // trigger get-user-media to load the device info at the first time const triggerCheckUserMedia = useRef(false); if (!triggerCheckUserMedia.current) { triggerCheckUserMedia.current = true; if (userMedia && availableInputDevices[0]?.label === '') { checkUserMedia(); } } const [outputDeviceIdState, setOutputDeviceState] = useDeviceIdState( outputDeviceId, availableOutputDevices, ); const [inputDeviceIdState, setInputDeviceState] = useDeviceIdState( inputDeviceId, availableInputDevices, ); const [isFirefox] = useState( navigator.userAgent.indexOf('Firefox') > -1, ); const [ringtoneVolumeState, setRingtoneVolumeState] = useState(ringtoneVolume); const [callVolumeState, setCallVolumeState] = useState(callVolume); const oldRingtoneVolume = usePrevious(() => ringtoneVolume, true); const oldCallVolume = usePrevious(() => callVolume, true); useEffect(() => { if (ringtoneVolume !== oldRingtoneVolume) { setRingtoneVolumeState(ringtoneVolume); } if (callVolume !== oldCallVolume) { setCallVolumeState(callVolume); } }, [ringtoneVolume, callVolume, oldRingtoneVolume, oldCallVolume]); const hasChanges = outputDeviceId !== outputDeviceIdState || inputDeviceId !== inputDeviceIdState || ringtoneVolume !== ringtoneVolumeState || callVolume !== callVolumeState; const onSaveClick = useCallback( () => onSave({ outputDeviceId: outputDeviceIdState, inputDeviceId: inputDeviceIdState, ringtoneVolume: ringtoneVolumeState, callVolume: callVolumeState, }), [ onSave, outputDeviceIdState, inputDeviceIdState, ringtoneVolumeState, callVolumeState, ], ); return (
{i18n.getString('title', currentLocale)} {supportDevices ? ( <> ) : null} {showCallVolume ? ( ) : null} {showRingToneVolume ? ( ) : null}
); };