(front) implement new ui of EffectsConfiguration

This new ui implement the new sketches and also enables the usage of
virtual background in a nice way.
This commit is contained in:
Nathan Vasse
2025-01-31 15:43:18 +01:00
committed by NathanVss
parent 08c5245b48
commit eff0cb4afb
24 changed files with 241 additions and 63 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 958 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 955 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 986 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -2,12 +2,16 @@ import { LocalVideoTrack } from 'livekit-client'
import { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import {
BackgroundBlurFactory,
BackgroundBlurProcessorInterface,
BackgroundProcessorFactory,
BackgroundProcessorInterface,
ProcessorType,
} from '../blur'
import { css } from '@/styled-system/css'
import { Text, P, ToggleButton, H } from '@/primitives'
import { HStack, styled } from '@/styled-system/jsx'
import { styled } from '@/styled-system/jsx'
import { BackgroundOptions } from '@livekit/track-processors'
import { BlurOn } from '@/components/icons/BlurOn'
import { BlurOnStrong } from '@/components/icons/BlurOnStrong'
enum BlurRadius {
NONE = 0,
@@ -15,21 +19,20 @@ enum BlurRadius {
NORMAL = 10,
}
const isSupported = BackgroundBlurFactory.isSupported()
const isSupported = BackgroundProcessorFactory.isSupported()
const Information = styled('div', {
base: {
backgroundColor: 'orange.50',
borderRadius: '4px',
padding: '0.75rem 0.75rem',
marginTop: '0.8rem',
alignItems: 'start',
},
})
export type EffectsConfigurationProps = {
videoTrack: LocalVideoTrack
onSubmit?: (processor?: BackgroundBlurProcessorInterface) => void
onSubmit?: (processor?: BackgroundProcessorInterface) => void
layout?: 'vertical' | 'horizontal'
}
@@ -55,46 +58,56 @@ export const EffectsConfiguration = ({
}
}, [videoTrack, videoTrack?.isMuted])
const toggleBlur = async (blurRadius: number) => {
const toggleEffect = async (
type: ProcessorType,
options: BackgroundOptions
) => {
if (!videoTrack) return
setProcessorPending(true)
const processor = getProcessor()
const currentBlurRadius = getBlurRadius()
try {
if (blurRadius == currentBlurRadius && processor) {
if (isSelected(type, options)) {
// Stop processor.
await videoTrack.stopProcessor()
onSubmit?.(undefined)
} else if (!processor) {
const newProcessor = BackgroundBlurFactory.getProcessor({ blurRadius })!
} else if (!processor || processor.serialize().type !== type) {
// Change processor.
const newProcessor = BackgroundProcessorFactory.getProcessor(
type,
options
)!
await videoTrack.setProcessor(newProcessor)
onSubmit?.(newProcessor)
} else {
processor?.update({ blurRadius })
// Update processor.
processor?.update(options)
// We want to trigger onSubmit when options changes so the parent component is aware of it.
onSubmit?.(processor)
}
} catch (error) {
console.error('Error applying blur:', error)
} finally {
setProcessorPending(false)
// Without setTimeout the DOM is not refreshing when updating the options.
setTimeout(() => setProcessorPending(false))
}
}
const getProcessor = () => {
return videoTrack?.getProcessor() as BackgroundBlurProcessorInterface
return videoTrack?.getProcessor() as BackgroundProcessorInterface
}
const getBlurRadius = (): BlurRadius => {
const isSelected = (type: ProcessorType, options: BackgroundOptions) => {
const processor = getProcessor()
return processor?.options.blurRadius || BlurRadius.NONE
const processorSerialized = processor?.serialize()
return (
!!processor &&
processorSerialized.type === type &&
JSON.stringify(processorSerialized.options) === JSON.stringify(options)
)
}
const isSelected = (blurRadius: BlurRadius) => {
return getBlurRadius() == blurRadius
}
const tooltipLabel = (blurRadius: BlurRadius) => {
return t(`blur.${isSelected(blurRadius) ? 'clear' : 'apply'}`)
const tooltipLabel = (type: ProcessorType, options: BackgroundOptions) => {
return t(`${type}.${isSelected(type, options) ? 'clear' : 'apply'}`)
}
return (
@@ -104,7 +117,7 @@ export const EffectsConfiguration = ({
? {
display: 'flex',
flexDirection: 'column',
gap: '0.5rem',
gap: '1.5rem',
}
: {
display: 'flex',
@@ -112,6 +125,7 @@ export const EffectsConfiguration = ({
flexDirection: 'column',
md: {
flexDirection: 'row',
overflow: 'hidden',
},
}
)}
@@ -164,49 +178,138 @@ export const EffectsConfiguration = ({
md: {
borderLeft: '1px solid #dadce0',
paddingLeft: '1.5rem',
width: '420px',
flexShrink: 0,
},
}
: {}
)}
>
<H
lvl={3}
style={{
marginBottom: '0.4rem',
fontWeight: 'bold',
}}
>
{t('heading')}
</H>
{isSupported ? (
<HStack>
<ToggleButton
size={'sm'}
aria-label={tooltipLabel(BlurRadius.LIGHT)}
tooltip={tooltipLabel(BlurRadius.LIGHT)}
isDisabled={processorPending}
onPress={async () => await toggleBlur(BlurRadius.LIGHT)}
isSelected={isSelected(BlurRadius.LIGHT)}
>
{t('blur.light')}
</ToggleButton>
<ToggleButton
size={'sm'}
aria-label={tooltipLabel(BlurRadius.NORMAL)}
tooltip={tooltipLabel(BlurRadius.NORMAL)}
isDisabled={processorPending}
onPress={async () => await toggleBlur(BlurRadius.NORMAL)}
isSelected={isSelected(BlurRadius.NORMAL)}
>
{t('blur.normal')}
</ToggleButton>
</HStack>
<>
<div>
<div>
<H
lvl={3}
style={{
marginBottom: '1rem',
}}
variant="bodyXsBold"
>
{t('blur.title')}
</H>
<div
className={css({
display: 'flex',
gap: '1.25rem',
})}
>
<ToggleButton
variant="bigSquare"
aria-label={tooltipLabel(ProcessorType.BLUR, {
blurRadius: BlurRadius.LIGHT,
})}
tooltip={tooltipLabel(ProcessorType.BLUR, {
blurRadius: BlurRadius.LIGHT,
})}
isDisabled={processorPending}
onChange={async () =>
await toggleEffect(ProcessorType.BLUR, {
blurRadius: BlurRadius.LIGHT,
})
}
isSelected={isSelected(ProcessorType.BLUR, {
blurRadius: BlurRadius.LIGHT,
})}
>
<BlurOn />
</ToggleButton>
<ToggleButton
variant="bigSquare"
aria-label={tooltipLabel(ProcessorType.BLUR, {
blurRadius: BlurRadius.NORMAL,
})}
tooltip={tooltipLabel(ProcessorType.BLUR, {
blurRadius: BlurRadius.NORMAL,
})}
isDisabled={processorPending}
onChange={async () =>
await toggleEffect(ProcessorType.BLUR, {
blurRadius: BlurRadius.NORMAL,
})
}
isSelected={isSelected(ProcessorType.BLUR, {
blurRadius: BlurRadius.NORMAL,
})}
>
<BlurOnStrong />
</ToggleButton>
</div>
</div>
<div
className={css({
marginTop: '1.5rem',
})}
>
<H
lvl={3}
style={{
marginBottom: '1rem',
}}
variant="bodyXsBold"
>
{t('virtual.title')}
</H>
<div
className={css({
display: 'flex',
gap: '1.25rem',
flexWrap: 'wrap',
})}
>
{[...Array(8).keys()].map((i) => {
const imagePath = `/assets/backgrounds/${i + 1}.jpg`
const thumbnailPath = `/assets/backgrounds/thumbnails/${i + 1}.jpg`
return (
<ToggleButton
key={i}
variant="bigSquare"
aria-label={tooltipLabel(ProcessorType.VIRTUAL, {
imagePath,
})}
tooltip={tooltipLabel(ProcessorType.VIRTUAL, {
imagePath,
})}
isDisabled={processorPending}
onChange={async () =>
await toggleEffect(ProcessorType.VIRTUAL, {
imagePath,
})
}
isSelected={isSelected(ProcessorType.VIRTUAL, {
imagePath,
})}
className={css({
bgSize: 'cover',
})}
style={{
backgroundImage: `url(${thumbnailPath})`,
}}
></ToggleButton>
)
})}
</div>
</div>
</div>
<Information className={css({ marginTop: '1rem' })}>
<Text variant="sm"> {t('experimental')}</Text>
</Information>
</>
) : (
<Text variant="sm">{t('notAvailable')}</Text>
<Information>
<Text variant="sm">{t('notAvailable')}</Text>
</Information>
)}
<Information>
<Text variant="sm"> {t('experimental')}</Text>
</Information>
</div>
</div>
)

View File

@@ -19,6 +19,11 @@
"label": ""
},
"heading": "",
"effects": {
"description": "",
"title": "",
"subTitle": ""
},
"joinLabel": "",
"joinMeeting": "",
"toggleOff": "",
@@ -102,11 +107,17 @@
"notAvailable": "",
"heading": "",
"blur": {
"title": "",
"light": "",
"normal": "",
"apply": "",
"clear": ""
},
"virtual": {
"title": "",
"apply": "",
"clear": ""
},
"experimental": ""
},
"sidePanel": {

View File

@@ -20,7 +20,8 @@
},
"effects": {
"description": "Apply effects",
"title": "Effects"
"title": "Effects",
"subTitle": "Configure your camera's effects."
},
"heading": "Join the meeting",
"joinLabel": "Join",
@@ -102,14 +103,20 @@
},
"effects": {
"activateCamera": "Your camera is disabled. Choose an option to enable it.",
"notAvailable": "The blur effect will be available soon on your browser. We're working on it! In the meantime, you can use Google Chrome for best performance or Firefox :(",
"notAvailable": "Video effects will be available soon on your browser. We're working on it! In the meantime, you can use Google Chrome for best performance or Firefox :(",
"heading": "Blur",
"blur": {
"title": "Background blur",
"light": "Light blur",
"normal": "Blur",
"apply": "Enable blur",
"clear": "Disable blur"
},
"virtual": {
"title": "Virtual background",
"apply": "Enable virtual background",
"clear": "Disable virtual background"
},
"experimental": "Experimental feature. A v2 is coming for full browser support and improved quality."
},
"sidePanel": {

View File

@@ -21,7 +21,8 @@
"heading": "Rejoindre la réunion",
"effects": {
"description": "Appliquer des effets",
"title": "Effets"
"title": "Effets",
"subTitle": "Paramétrez les effets de votre caméra."
},
"joinLabel": "Rejoindre",
"joinMeeting": "Rejoindre la réjoindre",
@@ -102,14 +103,20 @@
},
"effects": {
"activateCamera": "Votre camera est désactivée. Choisissez une option pour l'activer.",
"notAvailable": "L'effet de flou sera bientôt disponible sur votre navigateur. Nous y travaillons ! En attendant, vous pouvez utiliser Google Chrome pour une meilleure performance ou Firefox :(",
"notAvailable": "Les effets vidéo seront bientôt disponible sur votre navigateur. Nous y travaillons ! En attendant, vous pouvez utiliser Google Chrome pour une meilleure performance ou Firefox :(",
"heading": "Flou",
"blur": {
"title": "Flou d'arrière-plan",
"light": "Léger flou",
"normal": "Flou",
"apply": "Appliquer le flou",
"clear": "Désactiver le flou"
},
"virtual": {
"title": "Arrière-plan virtuel",
"apply": "Appliquer l'arrière plan virtuel",
"clear": "Désactiver l'arrière plan virtuel"
},
"experimental": "Fonctionnalité expérimentale. Une v2 arrive pour un support complet sur tous les navigateurs et une meilleur qualité."
},
"sidePanel": {

View File

@@ -27,7 +27,7 @@ export const ToggleButton = ({
<TooltipWrapper tooltip={tooltip} tooltipType={tooltipType}>
<RACToggleButton
{...componentProps}
className={buttonRecipe(variantProps)}
className={[buttonRecipe(variantProps), props.className].join(' ')}
>
<>
{componentProps.children as ReactNode}

View File

@@ -86,6 +86,22 @@ export const buttonRecipe = cva({
backgroundColor: 'greyscale.100/50',
},
},
bigSquare: {
width: '56px',
height: '56px',
borderColor: 'greyscale.200',
borderRadius: '4px',
backgroundColor: 'greyscale.50',
padding: '0',
flexShrink: 0,
'&[data-hovered]': {
backgroundColor: 'greyscale.100',
},
transition: 'box-shadow 0.2s ease-in-out',
'&[data-selected]': {
boxShadow: 'token(colors.primary.400) 0px 0px 0px 3px inset',
},
},
tertiary: {
backgroundColor: 'primary.100',
color: 'primary.800',