mirror of
https://github.com/vector-im/element-call.git
synced 2024-11-30 00:50:48 +08:00
Merge pull request #2803 from robintown/device-radio
Replace device dropdowns with radio buttons
This commit is contained in:
commit
8de96878c0
18
src/settings/DeviceSelection.module.css
Normal file
18
src/settings/DeviceSelection.module.css
Normal file
@ -0,0 +1,18 @@
|
||||
.selection {
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: var(--cpd-color-text-secondary);
|
||||
margin-block: var(--cpd-space-3x) 0;
|
||||
}
|
||||
|
||||
.separator {
|
||||
margin-block: 6px var(--cpd-space-4x);
|
||||
}
|
||||
|
||||
.options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--cpd-space-4x);
|
||||
}
|
71
src/settings/DeviceSelection.tsx
Normal file
71
src/settings/DeviceSelection.tsx
Normal file
@ -0,0 +1,71 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { ChangeEvent, FC, useCallback, useId } from "react";
|
||||
import {
|
||||
Heading,
|
||||
InlineField,
|
||||
Label,
|
||||
RadioControl,
|
||||
Separator,
|
||||
} from "@vector-im/compound-web";
|
||||
|
||||
import { MediaDevice } from "../livekit/MediaDevicesContext";
|
||||
import styles from "./DeviceSelection.module.css";
|
||||
|
||||
interface Props {
|
||||
devices: MediaDevice;
|
||||
caption: string;
|
||||
}
|
||||
|
||||
export const DeviceSelection: FC<Props> = ({ devices, caption }) => {
|
||||
const groupId = useId();
|
||||
const onChange = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
devices.select(e.target.value);
|
||||
},
|
||||
[devices],
|
||||
);
|
||||
|
||||
if (devices.available.length == 0) return null;
|
||||
|
||||
return (
|
||||
<div className={styles.selection}>
|
||||
<Heading
|
||||
type="body"
|
||||
weight="semibold"
|
||||
size="sm"
|
||||
as="h4"
|
||||
className={styles.title}
|
||||
>
|
||||
{caption}
|
||||
</Heading>
|
||||
<Separator className={styles.separator} />
|
||||
<div className={styles.options}>
|
||||
{devices.available.map(({ deviceId, label }, index) => (
|
||||
<InlineField
|
||||
key={deviceId}
|
||||
name={groupId}
|
||||
control={
|
||||
<RadioControl
|
||||
checked={deviceId === devices.selectedId}
|
||||
onChange={onChange}
|
||||
value={deviceId}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Label>
|
||||
{!!label && label.trim().length > 0
|
||||
? label
|
||||
: `${caption} ${index + 1}`}
|
||||
</Label>
|
||||
</InlineField>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -5,10 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { ChangeEvent, FC, ReactNode, useCallback } from "react";
|
||||
import { ChangeEvent, FC, useCallback } from "react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { Dropdown, Separator, Text } from "@vector-im/compound-web";
|
||||
import { Root as Form, Text } from "@vector-im/compound-web";
|
||||
|
||||
import { Modal } from "../Modal";
|
||||
import styles from "./SettingsModal.module.css";
|
||||
@ -19,7 +19,6 @@ import { ProfileSettingsTab } from "./ProfileSettingsTab";
|
||||
import { FeedbackSettingsTab } from "./FeedbackSettingsTab";
|
||||
import {
|
||||
useMediaDevices,
|
||||
MediaDevice,
|
||||
useMediaDeviceNames,
|
||||
} from "../livekit/MediaDevicesContext";
|
||||
import { widget } from "../widget";
|
||||
@ -33,6 +32,7 @@ import {
|
||||
import { isFirefox } from "../Platform";
|
||||
import { PreferencesSettingsTab } from "./PreferencesSettingsTab";
|
||||
import { Slider } from "../Slider";
|
||||
import { DeviceSelection } from "./DeviceSelection";
|
||||
|
||||
type SettingsTab =
|
||||
| "audio"
|
||||
@ -70,40 +70,6 @@ export const SettingsModal: FC<Props> = ({
|
||||
);
|
||||
const [duplicateTiles, setDuplicateTiles] = useSetting(duplicateTilesSetting);
|
||||
|
||||
// Generate a `SelectInput` with a list of devices for a given device kind.
|
||||
const generateDeviceSelection = (
|
||||
devices: MediaDevice,
|
||||
caption: string,
|
||||
): ReactNode => {
|
||||
if (devices.available.length == 0) return null;
|
||||
|
||||
const values = devices.available.map(
|
||||
({ deviceId, label }, index) =>
|
||||
[
|
||||
deviceId,
|
||||
!!label && label.trim().length > 0
|
||||
? label
|
||||
: `${caption} ${index + 1}`,
|
||||
] as [string, string],
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
label={caption}
|
||||
defaultValue={
|
||||
devices.selectedId === "" || !devices.selectedId
|
||||
? "default"
|
||||
: devices.selectedId
|
||||
}
|
||||
onValueChange={(id): void => devices.select(id)}
|
||||
values={values}
|
||||
// XXX This is unused because we set a defaultValue. The component
|
||||
// shouldn't require this prop.
|
||||
placeholder=""
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const optInDescription = (
|
||||
<Text size="sm">
|
||||
<Trans i18nKey="settings.opt_in_description">
|
||||
@ -125,25 +91,30 @@ export const SettingsModal: FC<Props> = ({
|
||||
name: t("common.audio"),
|
||||
content: (
|
||||
<>
|
||||
{generateDeviceSelection(devices.audioInput, t("common.microphone"))}
|
||||
{!isFirefox() &&
|
||||
generateDeviceSelection(
|
||||
devices.audioOutput,
|
||||
t("settings.speaker_device_selection_label"),
|
||||
)}
|
||||
<Separator />
|
||||
<div className={styles.volumeSlider}>
|
||||
<label>{t("settings.audio_tab.effect_volume_label")}</label>
|
||||
<p>{t("settings.audio_tab.effect_volume_description")}</p>
|
||||
<Slider
|
||||
label={t("video_tile.volume")}
|
||||
value={soundVolume}
|
||||
onValueChange={setSoundVolume}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
<Form>
|
||||
<DeviceSelection
|
||||
devices={devices.audioInput}
|
||||
caption={t("common.microphone")}
|
||||
/>
|
||||
</div>
|
||||
{!isFirefox() && (
|
||||
<DeviceSelection
|
||||
devices={devices.audioOutput}
|
||||
caption={t("settings.speaker_device_selection_label")}
|
||||
/>
|
||||
)}
|
||||
<div className={styles.volumeSlider}>
|
||||
<label>{t("settings.audio_tab.effect_volume_label")}</label>
|
||||
<p>{t("settings.audio_tab.effect_volume_description")}</p>
|
||||
<Slider
|
||||
label={t("video_tile.volume")}
|
||||
value={soundVolume}
|
||||
onValueChange={setSoundVolume}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
/>
|
||||
</div>
|
||||
</Form>
|
||||
</>
|
||||
),
|
||||
};
|
||||
@ -151,7 +122,14 @@ export const SettingsModal: FC<Props> = ({
|
||||
const videoTab: Tab<SettingsTab> = {
|
||||
key: "video",
|
||||
name: t("common.video"),
|
||||
content: generateDeviceSelection(devices.videoInput, t("common.camera")),
|
||||
content: (
|
||||
<Form>
|
||||
<DeviceSelection
|
||||
devices={devices.videoInput}
|
||||
caption={t("common.camera")}
|
||||
/>
|
||||
</Form>
|
||||
),
|
||||
};
|
||||
|
||||
const preferencesTab: Tab<SettingsTab> = {
|
||||
|
Loading…
Reference in New Issue
Block a user