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',