Merge branch 'livekit' into toger5/track-processor-blur

This commit is contained in:
Hugh Nimmo-Smith
2024-12-18 09:41:38 +00:00
71 changed files with 1337 additions and 1056 deletions

View File

@@ -13,6 +13,7 @@ import {
useSetting,
duplicateTiles as duplicateTilesSetting,
debugTileLayout as debugTileLayoutSetting,
showNonMemberTiles as showNonMemberTilesSetting,
} from "./settings";
import type { MatrixClient } from "matrix-js-sdk/src/client";
@@ -26,6 +27,9 @@ export const DeveloperSettingsTab: FC<Props> = ({ client }) => {
const [debugTileLayout, setDebugTileLayout] = useSetting(
debugTileLayoutSetting,
);
const [showNonMemberTiles, setShowNonMemberTiles] = useSetting(
showNonMemberTilesSetting,
);
return (
<>
@@ -85,6 +89,20 @@ export const DeveloperSettingsTab: FC<Props> = ({ client }) => {
}
/>
</FieldRow>
<FieldRow>
<InputField
id="showNonMemberTiles"
type="checkbox"
label={t("developer_mode.show_non_member_tiles")}
checked={!!showNonMemberTiles}
onChange={useCallback(
(event: ChangeEvent<HTMLInputElement>): void => {
setShowNonMemberTiles(event.target.checked);
},
[setShowNonMemberTiles],
)}
/>
</FieldRow>
</>
);
};

View File

@@ -16,3 +16,7 @@
flex-direction: column;
gap: var(--cpd-space-4x);
}
.secondary {
color: var(--cpd-color-text-secondary);
}

View File

@@ -5,7 +5,14 @@ SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
import { type ChangeEvent, type FC, useCallback, useId } from "react";
import {
type ChangeEvent,
type FC,
type ReactElement,
type ReactNode,
useCallback,
useId,
} from "react";
import {
Heading,
InlineField,
@@ -13,16 +20,23 @@ import {
RadioControl,
Separator,
} from "@vector-im/compound-web";
import { Trans, useTranslation } from "react-i18next";
import { type MediaDevice } from "../livekit/MediaDevicesContext";
import styles from "./DeviceSelection.module.css";
interface Props {
devices: MediaDevice;
caption: string;
title: string;
numberedLabel: (number: number) => string;
}
export const DeviceSelection: FC<Props> = ({ devices, caption }) => {
export const DeviceSelection: FC<Props> = ({
devices,
title,
numberedLabel,
}) => {
const { t } = useTranslation();
const groupId = useId();
const onChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
@@ -31,7 +45,7 @@ export const DeviceSelection: FC<Props> = ({ devices, caption }) => {
[devices],
);
if (devices.available.length == 0) return null;
if (devices.available.size == 0) return null;
return (
<div className={styles.selection}>
@@ -42,29 +56,53 @@ export const DeviceSelection: FC<Props> = ({ devices, caption }) => {
as="h4"
className={styles.title}
>
{caption}
{title}
</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>
))}
{[...devices.available].map(([id, label]) => {
let labelText: ReactNode;
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")
) : (
<Trans
i18nKey="settings.devices.default_named"
name={label.name}
>
Default{" "}
<span className={styles.secondary}>
({{ name: label.name } as unknown as ReactElement})
</span>
</Trans>
);
break;
}
return (
<InlineField
key={id}
name={groupId}
control={
<RadioControl
checked={id === devices.selectedId}
onChange={onChange}
value={id}
/>
}
>
<Label>{labelText}</Label>
</InlineField>
);
})}
</div>
</div>
);

View File

@@ -41,13 +41,13 @@ export const PreferencesSettingsTab: FC = () => {
return (
<div>
<Text>{t("settings.preferences_tab_body")}</Text>
<Text>{t("settings.preferences_tab.introduction")}</Text>
<FieldRow>
<InputField
id="showHandRaisedTimer"
label={t("settings.preferences_tab_show_hand_raised_timer_label")}
label={t("settings.preferences_tab.show_hand_raised_timer_label")}
description={t(
"settings.preferences_tab_show_hand_raised_timer_description",
"settings.preferences_tab.show_hand_raised_timer_description",
)}
type="checkbox"
checked={showHandRaisedTimer}

View File

@@ -26,7 +26,6 @@ import {
backgroundBlur as backgroundBlurSetting,
developerMode,
} from "./settings";
import { isFirefox } from "../Platform";
import { PreferencesSettingsTab } from "./PreferencesSettingsTab";
import { Slider } from "../Slider";
import { DeviceSelection } from "./DeviceSelection";
@@ -107,14 +106,16 @@ export const SettingsModal: FC<Props> = ({
<Form>
<DeviceSelection
devices={devices.audioInput}
caption={t("common.microphone")}
title={t("settings.devices.microphone")}
numberedLabel={(n) =>
t("settings.devices.microphone_numbered", { n })
}
/>
<DeviceSelection
devices={devices.audioOutput}
title={t("settings.devices.speaker")}
numberedLabel={(n) => t("settings.devices.speaker_numbered", { n })}
/>
{!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>
@@ -141,7 +142,8 @@ export const SettingsModal: FC<Props> = ({
<Form>
<DeviceSelection
devices={devices.videoInput}
caption={t("common.camera")}
title={t("settings.devices.camera")}
numberedLabel={(n) => t("settings.devices.camera_numbered", { n })}
/>
</Form>
<Separator />

View File

@@ -31,17 +31,17 @@ export class Setting<T> {
}
}
this._value = new BehaviorSubject(initialValue);
this.value = this._value;
this._value$ = new BehaviorSubject(initialValue);
this.value$ = this._value$;
}
private readonly key: string;
private readonly _value: BehaviorSubject<T>;
public readonly value: Observable<T>;
private readonly _value$: BehaviorSubject<T>;
public readonly value$: Observable<T>;
public readonly setValue = (value: T): void => {
this._value.next(value);
this._value$.next(value);
localStorage.setItem(this.key, JSON.stringify(value));
};
}
@@ -50,7 +50,7 @@ export class Setting<T> {
* React hook that returns a settings's current value and a setter.
*/
export function useSetting<T>(setting: Setting<T>): [T, (value: T) => void] {
return [useObservableEagerState(setting.value), setting.setValue];
return [useObservableEagerState(setting.value$), setting.setValue];
}
// null = undecided
@@ -72,6 +72,10 @@ export const developerMode = new Setting("developer-settings-tab", false);
export const duplicateTiles = new Setting("duplicate-tiles", 0);
export const showNonMemberTiles = new Setting<boolean>(
"show-non-member-tiles",
false,
);
export const debugTileLayout = new Setting("debug-tile-layout", false);
export const audioInput = new Setting<string | undefined>(