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.
|
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 { Trans, useTranslation } from "react-i18next";
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
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 { Modal } from "../Modal";
|
||||||
import styles from "./SettingsModal.module.css";
|
import styles from "./SettingsModal.module.css";
|
||||||
@ -19,7 +19,6 @@ import { ProfileSettingsTab } from "./ProfileSettingsTab";
|
|||||||
import { FeedbackSettingsTab } from "./FeedbackSettingsTab";
|
import { FeedbackSettingsTab } from "./FeedbackSettingsTab";
|
||||||
import {
|
import {
|
||||||
useMediaDevices,
|
useMediaDevices,
|
||||||
MediaDevice,
|
|
||||||
useMediaDeviceNames,
|
useMediaDeviceNames,
|
||||||
} from "../livekit/MediaDevicesContext";
|
} from "../livekit/MediaDevicesContext";
|
||||||
import { widget } from "../widget";
|
import { widget } from "../widget";
|
||||||
@ -33,6 +32,7 @@ import {
|
|||||||
import { isFirefox } from "../Platform";
|
import { isFirefox } from "../Platform";
|
||||||
import { PreferencesSettingsTab } from "./PreferencesSettingsTab";
|
import { PreferencesSettingsTab } from "./PreferencesSettingsTab";
|
||||||
import { Slider } from "../Slider";
|
import { Slider } from "../Slider";
|
||||||
|
import { DeviceSelection } from "./DeviceSelection";
|
||||||
|
|
||||||
type SettingsTab =
|
type SettingsTab =
|
||||||
| "audio"
|
| "audio"
|
||||||
@ -70,40 +70,6 @@ export const SettingsModal: FC<Props> = ({
|
|||||||
);
|
);
|
||||||
const [duplicateTiles, setDuplicateTiles] = useSetting(duplicateTilesSetting);
|
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 = (
|
const optInDescription = (
|
||||||
<Text size="sm">
|
<Text size="sm">
|
||||||
<Trans i18nKey="settings.opt_in_description">
|
<Trans i18nKey="settings.opt_in_description">
|
||||||
@ -125,25 +91,30 @@ export const SettingsModal: FC<Props> = ({
|
|||||||
name: t("common.audio"),
|
name: t("common.audio"),
|
||||||
content: (
|
content: (
|
||||||
<>
|
<>
|
||||||
{generateDeviceSelection(devices.audioInput, t("common.microphone"))}
|
<Form>
|
||||||
{!isFirefox() &&
|
<DeviceSelection
|
||||||
generateDeviceSelection(
|
devices={devices.audioInput}
|
||||||
devices.audioOutput,
|
caption={t("common.microphone")}
|
||||||
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}
|
|
||||||
/>
|
/>
|
||||||
</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> = {
|
const videoTab: Tab<SettingsTab> = {
|
||||||
key: "video",
|
key: "video",
|
||||||
name: t("common.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> = {
|
const preferencesTab: Tab<SettingsTab> = {
|
||||||
|
Loading…
Reference in New Issue
Block a user