✨(front) add loading state on effects component
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.
This commit is contained in:
@@ -12,6 +12,8 @@ import { styled } from '@/styled-system/jsx'
|
|||||||
import { BackgroundOptions } from '@livekit/track-processors'
|
import { BackgroundOptions } from '@livekit/track-processors'
|
||||||
import { BlurOn } from '@/components/icons/BlurOn'
|
import { BlurOn } from '@/components/icons/BlurOn'
|
||||||
import { BlurOnStrong } from '@/components/icons/BlurOnStrong'
|
import { BlurOnStrong } from '@/components/icons/BlurOnStrong'
|
||||||
|
import { Loader } from '@/primitives/Loader'
|
||||||
|
import { useSyncAfterDelay } from '@/hooks/useSyncAfterDelay'
|
||||||
|
|
||||||
enum BlurRadius {
|
enum BlurRadius {
|
||||||
NONE = 0,
|
NONE = 0,
|
||||||
@@ -44,6 +46,7 @@ export const EffectsConfiguration = ({
|
|||||||
const videoRef = useRef<HTMLVideoElement>(null)
|
const videoRef = useRef<HTMLVideoElement>(null)
|
||||||
const { t } = useTranslation('rooms', { keyPrefix: 'effects' })
|
const { t } = useTranslation('rooms', { keyPrefix: 'effects' })
|
||||||
const [processorPending, setProcessorPending] = useState(false)
|
const [processorPending, setProcessorPending] = useState(false)
|
||||||
|
const processorPendingReveal = useSyncAfterDelay(processorPending)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const videoElement = videoRef.current
|
const videoElement = videoRef.current
|
||||||
@@ -134,6 +137,7 @@ export const EffectsConfiguration = ({
|
|||||||
className={css({
|
className={css({
|
||||||
width: '100%',
|
width: '100%',
|
||||||
aspectRatio: 16 / 9,
|
aspectRatio: 16 / 9,
|
||||||
|
position: 'relative',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{videoTrack && !videoTrack.isMuted ? (
|
{videoTrack && !videoTrack.isMuted ? (
|
||||||
@@ -170,6 +174,17 @@ export const EffectsConfiguration = ({
|
|||||||
</P>
|
</P>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{processorPendingReveal && (
|
||||||
|
<div
|
||||||
|
className={css({
|
||||||
|
position: 'absolute',
|
||||||
|
right: '8px',
|
||||||
|
bottom: '8px',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Loader />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={css(
|
className={css(
|
||||||
@@ -212,7 +227,7 @@ export const EffectsConfiguration = ({
|
|||||||
tooltip={tooltipLabel(ProcessorType.BLUR, {
|
tooltip={tooltipLabel(ProcessorType.BLUR, {
|
||||||
blurRadius: BlurRadius.LIGHT,
|
blurRadius: BlurRadius.LIGHT,
|
||||||
})}
|
})}
|
||||||
isDisabled={processorPending}
|
isDisabled={processorPendingReveal}
|
||||||
onChange={async () =>
|
onChange={async () =>
|
||||||
await toggleEffect(ProcessorType.BLUR, {
|
await toggleEffect(ProcessorType.BLUR, {
|
||||||
blurRadius: BlurRadius.LIGHT,
|
blurRadius: BlurRadius.LIGHT,
|
||||||
@@ -232,7 +247,7 @@ export const EffectsConfiguration = ({
|
|||||||
tooltip={tooltipLabel(ProcessorType.BLUR, {
|
tooltip={tooltipLabel(ProcessorType.BLUR, {
|
||||||
blurRadius: BlurRadius.NORMAL,
|
blurRadius: BlurRadius.NORMAL,
|
||||||
})}
|
})}
|
||||||
isDisabled={processorPending}
|
isDisabled={processorPendingReveal}
|
||||||
onChange={async () =>
|
onChange={async () =>
|
||||||
await toggleEffect(ProcessorType.BLUR, {
|
await toggleEffect(ProcessorType.BLUR, {
|
||||||
blurRadius: BlurRadius.NORMAL,
|
blurRadius: BlurRadius.NORMAL,
|
||||||
@@ -280,7 +295,7 @@ export const EffectsConfiguration = ({
|
|||||||
tooltip={tooltipLabel(ProcessorType.VIRTUAL, {
|
tooltip={tooltipLabel(ProcessorType.VIRTUAL, {
|
||||||
imagePath,
|
imagePath,
|
||||||
})}
|
})}
|
||||||
isDisabled={processorPending}
|
isDisabled={processorPendingReveal}
|
||||||
onChange={async () =>
|
onChange={async () =>
|
||||||
await toggleEffect(ProcessorType.VIRTUAL, {
|
await toggleEffect(ProcessorType.VIRTUAL, {
|
||||||
imagePath,
|
imagePath,
|
||||||
|
|||||||
30
src/frontend/src/hooks/useSyncAfterDelay.ts
Normal file
30
src/frontend/src/hooks/useSyncAfterDelay.ts
Normal file
@@ -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<T>(value: T, waitFor: number = 300) {
|
||||||
|
const valueRef = useRef(value)
|
||||||
|
const timeoutRef = useRef<NodeJS.Timeout>()
|
||||||
|
const [syncValue, setSyncValue] = useState<T>()
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -101,6 +101,11 @@ export const buttonRecipe = cva({
|
|||||||
'&[data-selected]': {
|
'&[data-selected]': {
|
||||||
boxShadow: 'token(colors.primary.400) 0px 0px 0px 3px inset',
|
boxShadow: 'token(colors.primary.400) 0px 0px 0px 3px inset',
|
||||||
},
|
},
|
||||||
|
'&[data-disabled]': {
|
||||||
|
backgroundColor: 'greyscale.100',
|
||||||
|
color: 'greyscale.400',
|
||||||
|
opacity: '0.7',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
tertiary: {
|
tertiary: {
|
||||||
backgroundColor: 'primary.100',
|
backgroundColor: 'primary.100',
|
||||||
|
|||||||
Reference in New Issue
Block a user