mirror of
https://github.com/vector-im/element-call.git
synced 2026-09-22 22:29:30 +00:00
Wire the background effects grid to the pipeline
- Footer view model exposes the chosen effect and a setter for it - The footer names the catalogue: a view model holds no translated text - The camera menu shows the grid in place of the blur toggle - Shipped images are numbered, not named, since the art is a stand-in - Withholding the setter is what renders the section disabled, as with the speaker section where no output can be chosen Exploration for FEATURES_SPEC/2026-09_Background_Effects.md. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,9 @@
|
||||
},
|
||||
"action": {
|
||||
"add_background_image": "Add image",
|
||||
"background_effect_blur": "Blur",
|
||||
"background_effect_none": "None",
|
||||
"background_effect_numbered": "Background {{n}}",
|
||||
"blur_background": "Blur background",
|
||||
"close": "Close",
|
||||
"copy_link": "Copy link",
|
||||
|
||||
@@ -124,6 +124,8 @@ export const Default: Story = {
|
||||
toggleScreenSharing: fn(),
|
||||
toggleBlur: fn(),
|
||||
videoBlurEnabled: true,
|
||||
backgroundEffect: "none",
|
||||
selectBackgroundEffect: fn(),
|
||||
hangup: fn(),
|
||||
buttonSize: "lg",
|
||||
showFooter: true,
|
||||
|
||||
@@ -7,6 +7,7 @@ Please see LICENSE in the repository root for full details.
|
||||
|
||||
import { type FC, type JSX, type Ref, useMemo } from "react";
|
||||
import classNames from "classnames";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import LogoMark from "../icons/LogoMark.svg?react";
|
||||
import LogoType from "../icons/LogoType.svg?react";
|
||||
@@ -23,9 +24,11 @@ import {
|
||||
} from "../button";
|
||||
import styles from "./CallFooter.module.css";
|
||||
import {
|
||||
type BackgroundEffectOption,
|
||||
MediaMuteAndSwitchButton,
|
||||
type MenuOptions,
|
||||
} from "./MediaMuteAndSwitchButton";
|
||||
import { shippedBackgrounds } from "../livekit/backgroundEffects";
|
||||
import { type Behavior } from "../state/Behavior";
|
||||
import { type ViewModel } from "../state/ViewModel";
|
||||
import { useBehavior } from "../useBehavior";
|
||||
@@ -57,6 +60,8 @@ export interface FooterActions {
|
||||
/** Also controls if the videoMute button is disabled */
|
||||
toggleVideo: (() => void) | undefined;
|
||||
toggleBlur: (() => void) | undefined;
|
||||
/** Undefined where background processing is unavailable. */
|
||||
selectBackgroundEffect: ((id: string) => void) | undefined;
|
||||
toggleScreenSharing: (() => void) | undefined;
|
||||
/** Also controls if the settings button is visible */
|
||||
openSettings: (() => void) | undefined;
|
||||
@@ -70,6 +75,8 @@ export interface FooterState {
|
||||
videoEnabled: boolean;
|
||||
videoBusy: boolean;
|
||||
videoBlurEnabled: boolean;
|
||||
/** The chosen background effect, in its stored form. */
|
||||
backgroundEffect: string;
|
||||
showFooter: boolean;
|
||||
|
||||
/* This is needed for WindowMode = "flat" */
|
||||
@@ -123,6 +130,7 @@ export const CallFooter: FC<FooterProps> = ({
|
||||
children,
|
||||
vm,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const asOverlay = useBehavior(vm.asOverlay$);
|
||||
const showFooter = useBehavior(vm.showFooter$);
|
||||
const hideControls = useBehavior(vm.hideControls$);
|
||||
@@ -151,8 +159,26 @@ export const CallFooter: FC<FooterProps> = ({
|
||||
const selectedAudioOutput = useBehavior(vm.selectedAudioOutput$);
|
||||
const selectAudioOutputOption = useBehavior(vm.selectAudioOutputOption$);
|
||||
const selectVideoButtonOption = useBehavior(vm.selectVideoButtonOption$);
|
||||
const toggleBlur = useBehavior(vm.toggleBlur$);
|
||||
const videoBlurEnabled = useBehavior(vm.videoBlurEnabled$);
|
||||
const backgroundEffect = useBehavior(vm.backgroundEffect$);
|
||||
const selectBackgroundEffect = useBehavior(vm.selectBackgroundEffect$);
|
||||
|
||||
// The catalogue is named here rather than in the view model: the names are
|
||||
// for reading, and a view model has no business holding translated text.
|
||||
const backgroundEffects = useMemo(
|
||||
(): BackgroundEffectOption[] => [
|
||||
{ id: "none", kind: "none", label: t("action.background_effect_none") },
|
||||
{ id: "blur", kind: "blur", label: t("action.background_effect_blur") },
|
||||
...shippedBackgrounds.map((background, i) => ({
|
||||
id: `image:${background.id}`,
|
||||
kind: "image" as const,
|
||||
// Numbered rather than named: the images are stand-ins, and naming
|
||||
// them here would invent names the design has not given them.
|
||||
label: t("action.background_effect_numbered", { n: i + 1 }),
|
||||
imageUrl: background.imagePath,
|
||||
})),
|
||||
],
|
||||
[t],
|
||||
);
|
||||
const buttonSize = useBehavior(vm.buttonSize$);
|
||||
const showLogo = useBehavior(vm.showLogo$);
|
||||
|
||||
@@ -214,8 +240,9 @@ export const CallFooter: FC<FooterProps> = ({
|
||||
options={videoOptions}
|
||||
selectedOption={selectedVideo}
|
||||
onSelect={selectVideoButtonOption}
|
||||
videoBlurToggleClick={toggleBlur}
|
||||
videoBlurEnabled={videoBlurEnabled}
|
||||
backgroundEffects={backgroundEffects}
|
||||
selectedBackgroundEffect={backgroundEffect}
|
||||
onSelectBackgroundEffect={selectBackgroundEffect}
|
||||
/>,
|
||||
);
|
||||
} else {
|
||||
|
||||
@@ -14,6 +14,7 @@ import { type MenuOptions } from "./MediaMuteAndSwitchButton";
|
||||
import { type MediaDevices } from "../state/MediaDevices";
|
||||
import {
|
||||
backgroundBlur as backgroundBlurSettings,
|
||||
backgroundEffect as backgroundEffectSetting,
|
||||
debugTileLayout as debugTileLayoutSetting,
|
||||
} from "../settings/settings";
|
||||
import { type Behavior, constant } from "../state/Behavior";
|
||||
@@ -76,6 +77,8 @@ function buildDeviceBehaviors(
|
||||
| "selectVideoButtonOption$"
|
||||
| "toggleBlur$"
|
||||
| "videoBlurEnabled$"
|
||||
| "backgroundEffect$"
|
||||
| "selectBackgroundEffect$"
|
||||
> {
|
||||
const options$ = (
|
||||
available$: Behavior<Map<string, MenuOptions["label"]>>,
|
||||
@@ -128,6 +131,20 @@ function buildDeviceBehaviors(
|
||||
),
|
||||
),
|
||||
videoBlurEnabled$: backgroundBlurSettings.value$,
|
||||
backgroundEffect$: backgroundEffectSetting.value$,
|
||||
// Withholding the callback is what renders the section disabled, the same
|
||||
// way the speaker section is disabled where no output can be chosen.
|
||||
selectBackgroundEffect$: scope.behavior(
|
||||
disableSwitcher$.pipe(
|
||||
map((switcherDisabled) =>
|
||||
!switcherDisabled && supportsBackgroundProcessors()
|
||||
? (id: string): void => {
|
||||
backgroundEffectSetting.setValue(id);
|
||||
}
|
||||
: undefined,
|
||||
),
|
||||
),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user