diff --git a/locales/en/app.json b/locales/en/app.json index a14663e9..82aca385 100644 --- a/locales/en/app.json +++ b/locales/en/app.json @@ -204,6 +204,7 @@ "change_device_button": "Change audio device", "default": "Default", "default_named": "Default <2>({{name}})", + "default_numbered": "Default {{n}}", "handset": "Handset", "loudspeaker": "Loudspeaker", "microphone": "Microphone", diff --git a/src/components/CallFooter.stories.tsx b/src/components/CallFooter.stories.tsx index 2c78c823..e9a7537c 100644 --- a/src/components/CallFooter.stories.tsx +++ b/src/components/CallFooter.stories.tsx @@ -98,7 +98,6 @@ export const Default: Story = { selectedVideo: undefined, selectAudioButtonOption: undefined, selectVideoButtonOption: undefined, - videoToggles: [], }, parameters: { layout: "fullscreen", @@ -135,12 +134,12 @@ export const WithAudioAndVideoOptions: Story = { audioEnabled: false, videoEnabled: true, audioOptions: [ - { label: "Microphone 1", id: "1" }, - { label: "Microphone 2", id: "2" }, + { label: { type: "name", name: "Microphone 1" }, id: "1" }, + { label: { type: "name", name: "Microphone 2" }, id: "2" }, ], videoOptions: [ - { label: "Camera 1", id: "1" }, - { label: "Camera 2", id: "2" }, + { label: { type: "name", name: "Camera 1" }, id: "1" }, + { label: { type: "name", name: "Camera 2" }, id: "2" }, ], selectedAudio: "2", selectedVideo: "1", diff --git a/src/components/CallFooterViewModel.test.ts b/src/components/CallFooterViewModel.test.ts index 35284efd..68dd9b07 100644 --- a/src/components/CallFooterViewModel.test.ts +++ b/src/components/CallFooterViewModel.test.ts @@ -123,11 +123,29 @@ describe("createCallFooterViewModel", () => { ); expect(vm.audioOptions$?.value).toEqual([ - { id: "mic1", label: "Audio Device 1" }, - { id: "mic2", label: "Microphone 2" }, + { + id: "mic1", + label: { + number: 1, + type: "number", + }, + }, + { + id: "mic2", + label: { + name: "Microphone 2", + type: "name", + }, + }, ]); expect(vm.videoOptions$?.value).toEqual([ - { id: "cam1", label: "Camera 1" }, + { + id: "cam1", + label: { + name: "Camera 1", + type: "name", + }, + }, ]); }); }); diff --git a/src/components/CallFooterViewModel.tsx b/src/components/CallFooterViewModel.tsx index a7aec8d5..e7f02ada 100644 --- a/src/components/CallFooterViewModel.tsx +++ b/src/components/CallFooterViewModel.tsx @@ -11,7 +11,6 @@ import { supportsBackgroundProcessors } from "@livekit/track-processors"; import { type CallViewModel } from "../state/CallViewModel/CallViewModel"; import { type MenuOptions } from "./MediaMuteAndSwitchButton"; import { type MediaDevices } from "../state/MediaDevices"; -import { mediaDeviceLabelToString } from "../settings/DeviceSelection"; import { backgroundBlur as backgroundBlurSettings, debugTileLayout as debugTileLayoutSetting, @@ -77,10 +76,7 @@ function buildDeviceBehaviors( map((available) => [...available.entries()].map(([id, label]) => ({ id, - label: mediaDeviceLabelToString( - label, - (n) => "Audio Device " + n, - ), + label, })), ), ), @@ -100,10 +96,7 @@ function buildDeviceBehaviors( map((available) => [...available.entries()].map(([id, label]) => ({ id, - label: mediaDeviceLabelToString( - label, - (n) => "Camera " + n, - ), + label, })), ), ), diff --git a/src/components/MediaMuteAndSwitchButton.stories.tsx b/src/components/MediaMuteAndSwitchButton.stories.tsx index fe68fc9b..9aac05ea 100644 --- a/src/components/MediaMuteAndSwitchButton.stories.tsx +++ b/src/components/MediaMuteAndSwitchButton.stories.tsx @@ -23,8 +23,8 @@ export const Default: Story = { iconsAndLabels: "audio", enabled: true, options: [ - { label: "option 1", id: "1" }, - { label: "option 2", id: "2" }, + { label: { type: "name", name: "Option 1" }, id: "1" }, + { label: { type: "name", name: "Option 2" }, id: "2" }, ], selectedOption: "1", onMuteClick: fn(), @@ -39,16 +39,11 @@ export const AudioMute: Story = { iconsAndLabels: "audio", enabled: false, options: [ - { label: "Microphone 1", id: "1" }, - { label: "Microphone 2", id: "2" }, - ], - toggles: [ - { - label: "example toggle", - id: "t0", - enabled: true, - }, + { label: { type: "name", name: "Microphone 1" }, id: "1" }, + { label: { type: "name", name: "Microphone 2" }, id: "2" }, ], + videoBlurEnabled: true, + backgroundBlurToggleClick: fn(), selectedOption: "2", }, play: async ({ args, canvasElement }) => { @@ -67,10 +62,10 @@ export const AudioUnmute: Story = { iconsAndLabels: "audio", enabled: true, options: [ - { label: "Microphone 1", id: "1" }, - { label: "Microphone 2", id: "2" }, + { label: { type: "name", name: "Microphone 1" }, id: "1" }, + { label: { type: "name", name: "Microphone 2" }, id: "2" }, ], - toggles: [], + selectedOption: "2", }, }; @@ -81,10 +76,10 @@ export const VideoMute: Story = { iconsAndLabels: "video", enabled: false, options: [ - { label: "Camera 1", id: "1" }, - { label: "Camera 2", id: "2" }, + { label: { type: "name", name: "Camera 1" }, id: "1" }, + { label: { type: "name", name: "Camera 2" }, id: "2" }, ], - toggles: [], + selectedOption: "1", }, }; @@ -95,16 +90,11 @@ export const VideoUnmute: Story = { iconsAndLabels: "video", enabled: true, options: [ - { label: "Camera 1", id: "1" }, - { label: "Camera 2", id: "2" }, - ], - toggles: [ - { - label: "Background blur", - id: "background_blurring", - enabled: false, - }, + { label: { type: "name", name: "Camera 1" }, id: "1" }, + { label: { type: "name", name: "Camera 2" }, id: "2" }, ], + videoBlurEnabled: true, + backgroundBlurToggleClick: fn(), selectedOption: "2", }, }; diff --git a/src/components/MediaMuteAndSwitchButton.test.tsx b/src/components/MediaMuteAndSwitchButton.test.tsx index 5dd3c2d4..471bf4d2 100644 --- a/src/components/MediaMuteAndSwitchButton.test.tsx +++ b/src/components/MediaMuteAndSwitchButton.test.tsx @@ -86,8 +86,8 @@ describe("MediaMuteAndSwitchButton", () => { iconsAndLabels="audio" enabled={true} options={[ - { label: "Microphone 1", id: "mic1" }, - { label: "Microphone 2", id: "mic2" }, + { label: { type: "name", name: "Microphone 1" }, id: "mic1" }, + { label: { type: "name", name: "Microphone 2" }, id: "mic2" }, ]} selectedOption="mic1" onSelect={onSelect} @@ -110,8 +110,8 @@ describe("MediaMuteAndSwitchButton", () => { iconsAndLabels="audio" enabled={true} options={[ - { label: "Microphone 1", id: "mic1" }, - { label: "Microphone 2", id: "mic2" }, + { label: { type: "name", name: "Microphone 1" }, id: "mic1" }, + { label: { type: "name", name: "Microphone 2" }, id: "mic2" }, ]} selectedOption="mic1" onSelect={onSelect} @@ -139,8 +139,8 @@ describe("MediaMuteAndSwitchButton", () => { iconsAndLabels="audio" enabled={true} options={[ - { label: "Microphone 1", id: "mic1" }, - { label: "Microphone 2", id: "mic2" }, + { label: { type: "name", name: "Microphone 1" }, id: "mic1" }, + { label: { type: "name", name: "Microphone 2" }, id: "mic2" }, ]} selectedOption={selectedOption} onSelect={(id) => { @@ -222,8 +222,8 @@ describe("MediaMuteAndSwitchButton", () => { iconsAndLabels="audio" enabled={true} options={[ - { label: "Microphone 1", id: "mic1" }, - { label: "Microphone 2", id: "mic2" }, + { label: { type: "name", name: "Microphone 1" }, id: "mic1" }, + { label: { type: "name", name: "Microphone 2" }, id: "mic2" }, ]} selectedOption="mic2" /> diff --git a/src/components/MediaMuteAndSwitchButton.tsx b/src/components/MediaMuteAndSwitchButton.tsx index c9c8a50d..4d4e7147 100644 --- a/src/components/MediaMuteAndSwitchButton.tsx +++ b/src/components/MediaMuteAndSwitchButton.tsx @@ -12,7 +12,6 @@ import { MenuItem, ToggleMenuItem, } from "@vector-im/compound-web"; -import { t } from "i18next"; import { CheckIcon, ChevronUpIcon, @@ -22,12 +21,14 @@ import { VideoCallIcon, } from "@vector-im/compound-design-tokens/assets/web/icons"; import classNames from "classnames"; +import { useTranslation } from "react-i18next"; import styles from "./MediaMuteAndSwitchButton.module.css"; import { MicButton, VideoButton } from "../button"; +import { type DeviceLabel } from "../state/MediaDevices"; export interface MenuOptions { - label: string; + label: DeviceLabel; id: string; } @@ -67,7 +68,7 @@ export const MediaMuteAndSwitchButton: FC = ({ }) => { const [plannedSelection, setPlannedSelection] = useState(null); const [menuOpen, setMenuOpen] = useState(false); - + const { t } = useTranslation(); let button; let toggles = backgroundBlurToggleClick === undefined @@ -152,35 +153,45 @@ export const MediaMuteAndSwitchButton: FC = ({ /> } > - {options?.map((option) => ( - - ) + {options?.map(({ label, id }) => { + const labelText = ((): string => { + switch (label.type) { + case "name": + return label.name; + case "number": + return t("settings.devices.default_numbered", { + n: label.number, + }); } - onSelect={(e) => { - e.preventDefault(); - if (option.id === selectedOption) return; - setPlannedSelection(option.id); - onSelect?.(option.id); - }} - key={option.id} - > - {selectedOption === option.id && ( - - )} - {selectedOption !== option.id && plannedSelection === option.id && ( - - )} - - ))} + })(); + return ( + + ) + } + onSelect={(e) => { + e.preventDefault(); + if (id === selectedOption) return; + setPlannedSelection(id); + onSelect?.(id); + }} + key={id} + > + {selectedOption === id && } + {selectedOption !== id && plannedSelection === id && ( + + )} + + ); + })} {(toggles?.length ?? 0) > 0 &&
} {toggles?.map((toggle) => ( { e2eeSystem: EncryptionSystem; // TODO refactor those reasons into an enum diff --git a/src/settings/DeviceSelection.tsx b/src/settings/DeviceSelection.tsx index f5cb8584..9245fe2b 100644 --- a/src/settings/DeviceSelection.tsx +++ b/src/settings/DeviceSelection.tsx @@ -14,7 +14,7 @@ import { Separator, } from "@vector-im/compound-web"; import { useObservableEagerState } from "observable-hooks"; -import { t } from "i18next"; +import { useTranslation } from "react-i18next"; import { type AudioOutputDeviceLabel, @@ -30,42 +30,11 @@ interface Props { numberedLabel: (number: number) => string; } -export function mediaDeviceLabelToString( - label: DeviceLabel | AudioOutputDeviceLabel, - numberedLabel: (number: number) => string, -): string { - let labelText = ""; - switch (label.type) { - case "name": - labelText = label.name; - break; - case "number": - labelText = numberedLabel(label.number); - break; - case "default": - labelText = - label.name === null - ? t("settings.devices.default") - : t("settings.devices.default_named", { name: label.name }); - break; - case "speaker": - labelText = t("settings.devices.loudspeaker"); - break; - case "earpiece": - labelText = t("settings.devices.handset"); - break; - } - return labelText; -} - -export const DeviceSelection: FC = ({ - device, - title, - numberedLabel, -}) => { +export const DeviceSelection: FC = ({ device, title }) => { const groupId = useId(); const available = useObservableEagerState(device.available$); const selectedId = useObservableEagerState(device.selected$)?.id; + const { t } = useTranslation(); const onChange = useCallback( (e: ChangeEvent) => { device.select(e.target.value); @@ -90,7 +59,24 @@ export const DeviceSelection: FC = ({
{[...available].map(([id, label]) => { - const labelText = mediaDeviceLabelToString(label, numberedLabel); + const labelText = ((): string => { + switch (label.type) { + case "name": + return label.name; + case "number": + return t("settings.devices.default_numbered", { + n: label.number, + }); + case "default": + return label.name === null + ? t("settings.devices.default") + : t("settings.devices.default_named", label.name); + case "speaker": + return t("settings.devices.loudspeaker"); + case "earpiece": + return t("settings.devices.handset"); + } + })(); return (