From c7c44eea4e00c27816f1df55ce06c5d214982981 Mon Sep 17 00:00:00 2001 From: fkwp Date: Thu, 17 Sep 2026 19:56:15 +0200 Subject: [PATCH] Put an added background on before joining, not during a call - Before joining nobody sees the change, so the picture goes on at once - In a call it waits to be chosen: otherwise choosing a file would change what everyone sees with no further word from the user - Teams draws the line in the same place, which is what settled it: a background added before joining applies by itself there, one added in a meeting has to be applied - The footer learns which side of joining it is on from its view model, since the lobby and the call build their own Co-Authored-By: Claude Opus 5 (1M context) --- src/components/CallFooter.stories.tsx | 1 + src/components/CallFooter.tsx | 42 ++++++++++++++++---------- src/components/CallFooterViewModel.tsx | 2 ++ 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/components/CallFooter.stories.tsx b/src/components/CallFooter.stories.tsx index 3dcfe48ce..93dbe562d 100644 --- a/src/components/CallFooter.stories.tsx +++ b/src/components/CallFooter.stories.tsx @@ -136,6 +136,7 @@ export const Default: Story = { videoBlurEnabled: true, backgroundEffect: "none", selectBackgroundEffect: fn(), + beforeJoining: false, hangup: fn(), buttonSize: "lg", showFooter: true, diff --git a/src/components/CallFooter.tsx b/src/components/CallFooter.tsx index 645955995..7c7dfdf48 100644 --- a/src/components/CallFooter.tsx +++ b/src/components/CallFooter.tsx @@ -94,6 +94,12 @@ export interface FooterState { videoBlurEnabled: boolean; /** The chosen background effect, in its stored form. */ backgroundEffect: string; + /** + * Whether this footer is shown before joining. An image added there becomes + * the background at once; one added in a call waits to be chosen, because + * putting it on would change what everyone sees with no further word. + */ + beforeJoining: boolean; showFooter: boolean; /* This is needed for WindowMode = "flat" */ @@ -178,6 +184,7 @@ export const CallFooter: FC = ({ const selectVideoButtonOption = useBehavior(vm.selectVideoButtonOption$); const backgroundEffect = useBehavior(vm.backgroundEffect$); const selectBackgroundEffect = useBehavior(vm.selectBackgroundEffect$); + const beforeJoining = useBehavior(vm.beforeJoining$); const { added, addBackground, removeBackground } = useAddedBackgrounds(); const [backgroundEffectError, setBackgroundEffectError] = useState< string | undefined @@ -214,24 +221,27 @@ export const CallFooter: FC = ({ const onAddBackgroundImage = useCallback( (file: File): void => { - // Added, not chosen. Whether picking a file should also put it on is - // still open, so this does the smaller thing: the image appears among - // the others and the user says when to wear it. setBackgroundEffectError(undefined); - addBackground(file).catch((e) => { - // TODO: FR-021 wants the user told what went wrong. There is no - // surface for that in the menu yet, and inventing one is design's - // call, so for now this is only logged. - setBackgroundEffectError(whyRefused(e)); - logger.warn( - e instanceof UnusableImage - ? `Cannot use that file as a background: ${e.reason}` - : "Could not keep that background", - e, - ); - }); + addBackground(file) + .then((id) => { + // Before joining, nobody sees the change, so the picture goes on at + // once. In a call it waits to be chosen: otherwise choosing a file + // would change what everyone sees, with no further word from the + // user. Teams draws the line in the same place. + if (beforeJoining) + selectBackgroundEffect?.(serializeEffect({ kind: "added", id })); + }) + .catch((e) => { + setBackgroundEffectError(whyRefused(e)); + logger.warn( + e instanceof UnusableImage + ? `Cannot use that file as a background: ${e.reason}` + : "Could not keep that background", + e, + ); + }); }, - [addBackground, whyRefused], + [addBackground, beforeJoining, selectBackgroundEffect, whyRefused], ); // The catalogue is named here rather than in the view model: the names are diff --git a/src/components/CallFooterViewModel.tsx b/src/components/CallFooterViewModel.tsx index c6588aebb..467a6cb0c 100644 --- a/src/components/CallFooterViewModel.tsx +++ b/src/components/CallFooterViewModel.tsx @@ -180,6 +180,7 @@ export function createCallFooterViewModel( return { ...buildMuteBehaviors(scope, muteStates), ...buildDeviceBehaviors(scope, mediaDevices, disableDeviceSwitcher$), + beforeJoining$: constant(false), // candidat to move into the FooterViewModel showFooter$: callModel.showFooter$, hideControls$: constant(!showControls), @@ -293,5 +294,6 @@ export function createLobbyFooterViewModel( }), ...buildMuteBehaviors(scope, muteStates), ...buildDeviceBehaviors(scope, mediaDevices, constant(false)), + beforeJoining$: constant(true), }; }