From e47b027bbcd93800d9c8c6b9fe09b6f96175a280 Mon Sep 17 00:00:00 2001 From: Nathan Vasse Date: Tue, 4 Feb 2025 17:53:02 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(front)=20add=20loading=20state=20on?= =?UTF-8?q?=20effects=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit useSyncAfterDelay allows to enable loading indicators only if the loading takes more than a specific time. It prevent blinking effect when the loading time is nearly instant. --- .../effects/EffectsConfiguration.tsx | 21 +++++++++++-- src/frontend/src/hooks/useSyncAfterDelay.ts | 30 +++++++++++++++++++ src/frontend/src/primitives/buttonRecipe.ts | 5 ++++ 3 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 src/frontend/src/hooks/useSyncAfterDelay.ts diff --git a/src/frontend/src/features/rooms/livekit/components/effects/EffectsConfiguration.tsx b/src/frontend/src/features/rooms/livekit/components/effects/EffectsConfiguration.tsx index bf7b6f1d..76eed2a0 100644 --- a/src/frontend/src/features/rooms/livekit/components/effects/EffectsConfiguration.tsx +++ b/src/frontend/src/features/rooms/livekit/components/effects/EffectsConfiguration.tsx @@ -12,6 +12,8 @@ import { styled } from '@/styled-system/jsx' import { BackgroundOptions } from '@livekit/track-processors' import { BlurOn } from '@/components/icons/BlurOn' import { BlurOnStrong } from '@/components/icons/BlurOnStrong' +import { Loader } from '@/primitives/Loader' +import { useSyncAfterDelay } from '@/hooks/useSyncAfterDelay' enum BlurRadius { NONE = 0, @@ -44,6 +46,7 @@ export const EffectsConfiguration = ({ const videoRef = useRef(null) const { t } = useTranslation('rooms', { keyPrefix: 'effects' }) const [processorPending, setProcessorPending] = useState(false) + const processorPendingReveal = useSyncAfterDelay(processorPending) useEffect(() => { const videoElement = videoRef.current @@ -134,6 +137,7 @@ export const EffectsConfiguration = ({ className={css({ width: '100%', aspectRatio: 16 / 9, + position: 'relative', })} > {videoTrack && !videoTrack.isMuted ? ( @@ -170,6 +174,17 @@ export const EffectsConfiguration = ({

)} + {processorPendingReveal && ( +
+ +
+ )}
await toggleEffect(ProcessorType.BLUR, { blurRadius: BlurRadius.LIGHT, @@ -232,7 +247,7 @@ export const EffectsConfiguration = ({ tooltip={tooltipLabel(ProcessorType.BLUR, { blurRadius: BlurRadius.NORMAL, })} - isDisabled={processorPending} + isDisabled={processorPendingReveal} onChange={async () => await toggleEffect(ProcessorType.BLUR, { blurRadius: BlurRadius.NORMAL, @@ -280,7 +295,7 @@ export const EffectsConfiguration = ({ tooltip={tooltipLabel(ProcessorType.VIRTUAL, { imagePath, })} - isDisabled={processorPending} + isDisabled={processorPendingReveal} onChange={async () => await toggleEffect(ProcessorType.VIRTUAL, { imagePath, diff --git a/src/frontend/src/hooks/useSyncAfterDelay.ts b/src/frontend/src/hooks/useSyncAfterDelay.ts new file mode 100644 index 00000000..32eddded --- /dev/null +++ b/src/frontend/src/hooks/useSyncAfterDelay.ts @@ -0,0 +1,30 @@ +import { useEffect, useRef, useState } from 'react' + +/** + * If value stays truthy for more than waitFor ms, syncValue takes the value of value. + * @param value + * @param waitFor + * @returns + */ +export function useSyncAfterDelay(value: T, waitFor: number = 300) { + const valueRef = useRef(value) + const timeoutRef = useRef() + const [syncValue, setSyncValue] = useState() + + useEffect(() => { + valueRef.current = value + if (value) { + if (!timeoutRef.current) { + timeoutRef.current = setTimeout(() => { + setSyncValue(valueRef.current) + timeoutRef.current = undefined + }, waitFor) + } + } else { + setSyncValue(value) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [value]) + + return syncValue +} diff --git a/src/frontend/src/primitives/buttonRecipe.ts b/src/frontend/src/primitives/buttonRecipe.ts index 52b3d9bb..b704968d 100644 --- a/src/frontend/src/primitives/buttonRecipe.ts +++ b/src/frontend/src/primitives/buttonRecipe.ts @@ -101,6 +101,11 @@ export const buttonRecipe = cva({ '&[data-selected]': { boxShadow: 'token(colors.primary.400) 0px 0px 0px 3px inset', }, + '&[data-disabled]': { + backgroundColor: 'greyscale.100', + color: 'greyscale.400', + opacity: '0.7', + }, }, tertiary: { backgroundColor: 'primary.100',