mirror of
https://github.com/vector-im/element-call.git
synced 2024-11-24 00:38:31 +08:00
Refactor settings to use observables
Also removing some unused settings along the way.
This commit is contained in:
parent
20602c122b
commit
41083c0f9e
@ -66,6 +66,7 @@
|
|||||||
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#238eea0ef5c82d0a11b8d5cc5c04104d6c94c4c1",
|
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#238eea0ef5c82d0a11b8d5cc5c04104d6c94c4c1",
|
||||||
"matrix-widget-api": "^1.3.1",
|
"matrix-widget-api": "^1.3.1",
|
||||||
"normalize.css": "^8.0.1",
|
"normalize.css": "^8.0.1",
|
||||||
|
"observable-hooks": "^4.2.3",
|
||||||
"pako": "^2.0.4",
|
"pako": "^2.0.4",
|
||||||
"postcss-preset-env": "^9.0.0",
|
"postcss-preset-env": "^9.0.0",
|
||||||
"posthog-js": "^1.29.0",
|
"posthog-js": "^1.29.0",
|
||||||
|
@ -140,7 +140,6 @@
|
|||||||
"feedback_tab_title": "Feedback",
|
"feedback_tab_title": "Feedback",
|
||||||
"more_tab_title": "More",
|
"more_tab_title": "More",
|
||||||
"opt_in_description": "<0></0><1></1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.",
|
"opt_in_description": "<0></0><1></1>You may withdraw consent by unchecking this box. If you are currently in a call, this setting will take effect at the end of the call.",
|
||||||
"show_connection_stats_label": "Show connection stats",
|
|
||||||
"speaker_device_selection_label": "Speaker"
|
"speaker_device_selection_label": "Speaker"
|
||||||
},
|
},
|
||||||
"star_rating_input_label_one": "{{count}} stars",
|
"star_rating_input_label_one": "{{count}} stars",
|
||||||
|
@ -20,7 +20,6 @@ import { MatrixClient } from "matrix-js-sdk";
|
|||||||
import { Buffer } from "buffer";
|
import { Buffer } from "buffer";
|
||||||
|
|
||||||
import { widget } from "../widget";
|
import { widget } from "../widget";
|
||||||
import { getSetting, setSetting, getSettingKey } from "../settings/useSetting";
|
|
||||||
import {
|
import {
|
||||||
CallEndedTracker,
|
CallEndedTracker,
|
||||||
CallStartedTracker,
|
CallStartedTracker,
|
||||||
@ -35,7 +34,7 @@ import {
|
|||||||
} from "./PosthogEvents";
|
} from "./PosthogEvents";
|
||||||
import { Config } from "../config/Config";
|
import { Config } from "../config/Config";
|
||||||
import { getUrlParams } from "../UrlParams";
|
import { getUrlParams } from "../UrlParams";
|
||||||
import { localStorageBus } from "../useLocalStorage";
|
import { optInAnalytics } from "../settings/settings";
|
||||||
|
|
||||||
/* Posthog analytics tracking.
|
/* Posthog analytics tracking.
|
||||||
*
|
*
|
||||||
@ -131,7 +130,7 @@ export class PosthogAnalytics {
|
|||||||
const { analyticsID } = getUrlParams();
|
const { analyticsID } = getUrlParams();
|
||||||
// if the embedding platform (element web) already got approval to communicating with posthog
|
// if the embedding platform (element web) already got approval to communicating with posthog
|
||||||
// element call can also send events to posthog
|
// element call can also send events to posthog
|
||||||
setSetting("opt-in-analytics", Boolean(analyticsID));
|
optInAnalytics.setValue(Boolean(analyticsID));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.posthog.init(posthogConfig.project_api_key, {
|
this.posthog.init(posthogConfig.project_api_key, {
|
||||||
@ -151,9 +150,7 @@ export class PosthogAnalytics {
|
|||||||
);
|
);
|
||||||
this.enabled = false;
|
this.enabled = false;
|
||||||
}
|
}
|
||||||
this.startListeningToSettingsChanges();
|
this.startListeningToSettingsChanges(); // Triggers maybeIdentifyUser
|
||||||
const optInAnalytics = getSetting("opt-in-analytics", false);
|
|
||||||
this.updateAnonymityAndIdentifyUser(optInAnalytics);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private sanitizeProperties = (
|
private sanitizeProperties = (
|
||||||
@ -336,8 +333,7 @@ export class PosthogAnalytics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public onLoginStatusChanged(): void {
|
public onLoginStatusChanged(): void {
|
||||||
const optInAnalytics = getSetting("opt-in-analytics", false);
|
this.maybeIdentifyUser();
|
||||||
this.updateAnonymityAndIdentifyUser(optInAnalytics);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateSuperProperties(): void {
|
private updateSuperProperties(): void {
|
||||||
@ -360,20 +356,12 @@ export class PosthogAnalytics {
|
|||||||
return this.eventSignup.getSignupEndTime() > new Date(0);
|
return this.eventSignup.getSignupEndTime() > new Date(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateAnonymityAndIdentifyUser(
|
private async maybeIdentifyUser(): Promise<void> {
|
||||||
pseudonymousOptIn: boolean,
|
|
||||||
): Promise<void> {
|
|
||||||
// Update this.anonymity based on the user's analytics opt-in settings
|
|
||||||
const anonymity = pseudonymousOptIn
|
|
||||||
? Anonymity.Pseudonymous
|
|
||||||
: Anonymity.Disabled;
|
|
||||||
this.setAnonymity(anonymity);
|
|
||||||
|
|
||||||
// We may not yet have a Matrix client at this point, if not, bail. This should get
|
// We may not yet have a Matrix client at this point, if not, bail. This should get
|
||||||
// triggered again by onLoginStatusChanged once we do have a client.
|
// triggered again by onLoginStatusChanged once we do have a client.
|
||||||
if (!window.matrixclient) return;
|
if (!window.matrixclient) return;
|
||||||
|
|
||||||
if (anonymity === Anonymity.Pseudonymous) {
|
if (this.anonymity === Anonymity.Pseudonymous) {
|
||||||
this.setRegistrationType(
|
this.setRegistrationType(
|
||||||
window.matrixclient.isGuest() || window.passwordlessUser
|
window.matrixclient.isGuest() || window.passwordlessUser
|
||||||
? RegistrationType.Guest
|
? RegistrationType.Guest
|
||||||
@ -389,7 +377,7 @@ export class PosthogAnalytics {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (anonymity !== Anonymity.Disabled) {
|
if (this.anonymity !== Anonymity.Disabled) {
|
||||||
this.updateSuperProperties();
|
this.updateSuperProperties();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -419,8 +407,9 @@ export class PosthogAnalytics {
|
|||||||
// * When the user changes their preferences on this device
|
// * When the user changes their preferences on this device
|
||||||
// Note that for new accounts, pseudonymousAnalyticsOptIn won't be set, so updateAnonymityFromSettings
|
// Note that for new accounts, pseudonymousAnalyticsOptIn won't be set, so updateAnonymityFromSettings
|
||||||
// won't be called (i.e. this.anonymity will be left as the default, until the setting changes)
|
// won't be called (i.e. this.anonymity will be left as the default, until the setting changes)
|
||||||
localStorageBus.on(getSettingKey("opt-in-analytics"), (optInAnalytics) => {
|
optInAnalytics.value.subscribe((optIn) => {
|
||||||
this.updateAnonymityAndIdentifyUser(optInAnalytics);
|
this.setAnonymity(optIn ? Anonymity.Pseudonymous : Anonymity.Disabled);
|
||||||
|
this.maybeIdentifyUser();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,9 +38,12 @@ import { UserMenuContainer } from "../UserMenuContainer";
|
|||||||
import { JoinExistingCallModal } from "./JoinExistingCallModal";
|
import { JoinExistingCallModal } from "./JoinExistingCallModal";
|
||||||
import { Caption } from "../typography/Typography";
|
import { Caption } from "../typography/Typography";
|
||||||
import { Form } from "../form/Form";
|
import { Form } from "../form/Form";
|
||||||
import { useOptInAnalytics } from "../settings/useSetting";
|
|
||||||
import { AnalyticsNotice } from "../analytics/AnalyticsNotice";
|
import { AnalyticsNotice } from "../analytics/AnalyticsNotice";
|
||||||
import { E2eeType } from "../e2ee/e2eeType";
|
import { E2eeType } from "../e2ee/e2eeType";
|
||||||
|
import {
|
||||||
|
useSetting,
|
||||||
|
optInAnalytics as optInAnalyticsSetting,
|
||||||
|
} from "../settings/settings";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
client: MatrixClient;
|
client: MatrixClient;
|
||||||
@ -49,7 +52,7 @@ interface Props {
|
|||||||
export const RegisteredView: FC<Props> = ({ client }) => {
|
export const RegisteredView: FC<Props> = ({ client }) => {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<Error>();
|
const [error, setError] = useState<Error>();
|
||||||
const [optInAnalytics] = useOptInAnalytics();
|
const [optInAnalytics] = useSetting(optInAnalyticsSetting);
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [joinExistingCallModalOpen, setJoinExistingCallModalOpen] =
|
const [joinExistingCallModalOpen, setJoinExistingCallModalOpen] =
|
||||||
|
@ -41,15 +41,18 @@ import styles from "./UnauthenticatedView.module.css";
|
|||||||
import commonStyles from "./common.module.css";
|
import commonStyles from "./common.module.css";
|
||||||
import { generateRandomName } from "../auth/generateRandomName";
|
import { generateRandomName } from "../auth/generateRandomName";
|
||||||
import { AnalyticsNotice } from "../analytics/AnalyticsNotice";
|
import { AnalyticsNotice } from "../analytics/AnalyticsNotice";
|
||||||
import { useOptInAnalytics } from "../settings/useSetting";
|
|
||||||
import { Config } from "../config/Config";
|
import { Config } from "../config/Config";
|
||||||
import { E2eeType } from "../e2ee/e2eeType";
|
import { E2eeType } from "../e2ee/e2eeType";
|
||||||
|
import {
|
||||||
|
useSetting,
|
||||||
|
optInAnalytics as optInAnalyticsSetting,
|
||||||
|
} from "../settings/settings";
|
||||||
|
|
||||||
export const UnauthenticatedView: FC = () => {
|
export const UnauthenticatedView: FC = () => {
|
||||||
const { setClient } = useClient();
|
const { setClient } = useClient();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<Error>();
|
const [error, setError] = useState<Error>();
|
||||||
const [optInAnalytics] = useOptInAnalytics();
|
const [optInAnalytics] = useSetting(optInAnalyticsSetting);
|
||||||
const { recaptchaKey, register } = useInteractiveRegistration();
|
const { recaptchaKey, register } = useInteractiveRegistration();
|
||||||
const { execute, reset, recaptchaId } = useRecaptcha(recaptchaKey);
|
const { execute, reset, recaptchaId } = useRecaptcha(recaptchaKey);
|
||||||
|
|
||||||
|
@ -29,11 +29,12 @@ import { Observable } from "rxjs";
|
|||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
useSetting,
|
||||||
|
audioInput as audioInputSetting,
|
||||||
|
audioOutput as audioOutputSetting,
|
||||||
|
videoInput as videoInputSetting,
|
||||||
isFirefox,
|
isFirefox,
|
||||||
useAudioInput,
|
} from "../settings/settings";
|
||||||
useAudioOutput,
|
|
||||||
useVideoInput,
|
|
||||||
} from "../settings/useSetting";
|
|
||||||
|
|
||||||
export interface MediaDevice {
|
export interface MediaDevice {
|
||||||
available: MediaDeviceInfo[];
|
available: MediaDeviceInfo[];
|
||||||
@ -145,43 +146,36 @@ export const MediaDevicesProvider: FC<Props> = ({ children }) => {
|
|||||||
// for ouput devices because the selector wont be shown on FF.
|
// for ouput devices because the selector wont be shown on FF.
|
||||||
const useOutputNames = usingNames && !isFirefox();
|
const useOutputNames = usingNames && !isFirefox();
|
||||||
|
|
||||||
const [audioInputSetting, setAudioInputSetting] = useAudioInput();
|
const [storedAudioInput, setStoredAudioInput] = useSetting(audioInputSetting);
|
||||||
const [audioOutputSetting, setAudioOutputSetting] = useAudioOutput();
|
const [storedAudioOutput, setStoredAudioOutput] =
|
||||||
const [videoInputSetting, setVideoInputSetting] = useVideoInput();
|
useSetting(audioOutputSetting);
|
||||||
|
const [storedVideoInput, setStoredVideoInput] = useSetting(videoInputSetting);
|
||||||
|
|
||||||
const audioInput = useMediaDevice(
|
const audioInput = useMediaDevice("audioinput", storedAudioInput, usingNames);
|
||||||
"audioinput",
|
|
||||||
audioInputSetting,
|
|
||||||
usingNames,
|
|
||||||
);
|
|
||||||
const audioOutput = useMediaDevice(
|
const audioOutput = useMediaDevice(
|
||||||
"audiooutput",
|
"audiooutput",
|
||||||
audioOutputSetting,
|
storedAudioOutput,
|
||||||
useOutputNames,
|
useOutputNames,
|
||||||
alwaysUseDefaultAudio,
|
alwaysUseDefaultAudio,
|
||||||
);
|
);
|
||||||
const videoInput = useMediaDevice(
|
const videoInput = useMediaDevice("videoinput", storedVideoInput, usingNames);
|
||||||
"videoinput",
|
|
||||||
videoInputSetting,
|
|
||||||
usingNames,
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (audioInput.selectedId !== undefined)
|
if (audioInput.selectedId !== undefined)
|
||||||
setAudioInputSetting(audioInput.selectedId);
|
setStoredAudioInput(audioInput.selectedId);
|
||||||
}, [setAudioInputSetting, audioInput.selectedId]);
|
}, [setStoredAudioInput, audioInput.selectedId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Skip setting state for ff output. Redundent since it is set to always return 'undefined'
|
// Skip setting state for ff output. Redundent since it is set to always return 'undefined'
|
||||||
// but makes it clear while debugging that this is not happening on FF. + perf ;)
|
// but makes it clear while debugging that this is not happening on FF. + perf ;)
|
||||||
if (audioOutput.selectedId !== undefined && !isFirefox())
|
if (audioOutput.selectedId !== undefined && !isFirefox())
|
||||||
setAudioOutputSetting(audioOutput.selectedId);
|
setStoredAudioOutput(audioOutput.selectedId);
|
||||||
}, [setAudioOutputSetting, audioOutput.selectedId]);
|
}, [setStoredAudioOutput, audioOutput.selectedId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (videoInput.selectedId !== undefined)
|
if (videoInput.selectedId !== undefined)
|
||||||
setVideoInputSetting(videoInput.selectedId);
|
setStoredVideoInput(videoInput.selectedId);
|
||||||
}, [setVideoInputSetting, videoInput.selectedId]);
|
}, [setStoredVideoInput, videoInput.selectedId]);
|
||||||
|
|
||||||
const startUsingDeviceNames = useCallback(
|
const startUsingDeviceNames = useCallback(
|
||||||
() => setNumCallersUsingNames((n) => n + 1),
|
() => setNumCallersUsingNames((n) => n + 1),
|
||||||
|
@ -26,7 +26,6 @@ import { GroupCallLoader } from "./GroupCallLoader";
|
|||||||
import { GroupCallView } from "./GroupCallView";
|
import { GroupCallView } from "./GroupCallView";
|
||||||
import { useRoomIdentifier, useUrlParams } from "../UrlParams";
|
import { useRoomIdentifier, useUrlParams } from "../UrlParams";
|
||||||
import { useRegisterPasswordlessUser } from "../auth/useRegisterPasswordlessUser";
|
import { useRegisterPasswordlessUser } from "../auth/useRegisterPasswordlessUser";
|
||||||
import { useOptInAnalytics } from "../settings/useSetting";
|
|
||||||
import { HomePage } from "../home/HomePage";
|
import { HomePage } from "../home/HomePage";
|
||||||
import { platform } from "../Platform";
|
import { platform } from "../Platform";
|
||||||
import { AppSelectionModal } from "./AppSelectionModal";
|
import { AppSelectionModal } from "./AppSelectionModal";
|
||||||
@ -36,6 +35,10 @@ import { LobbyView } from "./LobbyView";
|
|||||||
import { E2eeType } from "../e2ee/e2eeType";
|
import { E2eeType } from "../e2ee/e2eeType";
|
||||||
import { useProfile } from "../profile/useProfile";
|
import { useProfile } from "../profile/useProfile";
|
||||||
import { useMuteStates } from "./MuteStates";
|
import { useMuteStates } from "./MuteStates";
|
||||||
|
import {
|
||||||
|
useSetting,
|
||||||
|
optInAnalytics as optInAnalyticsSetting,
|
||||||
|
} from "../settings/settings";
|
||||||
|
|
||||||
export const RoomPage: FC = () => {
|
export const RoomPage: FC = () => {
|
||||||
const {
|
const {
|
||||||
@ -80,7 +83,7 @@ export const RoomPage: FC = () => {
|
|||||||
registerPasswordlessUser,
|
registerPasswordlessUser,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const [optInAnalytics, setOptInAnalytics] = useOptInAnalytics();
|
const [optInAnalytics, setOptInAnalytics] = useSetting(optInAnalyticsSetting);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// During the beta, opt into analytics by default
|
// During the beta, opt into analytics by default
|
||||||
if (optInAnalytics === null && setOptInAnalytics) setOptInAnalytics(true);
|
if (optInAnalytics === null && setOptInAnalytics) setOptInAnalytics(true);
|
||||||
|
@ -29,12 +29,6 @@ import OverflowIcon from "../icons/Overflow.svg?react";
|
|||||||
import UserIcon from "../icons/User.svg?react";
|
import UserIcon from "../icons/User.svg?react";
|
||||||
import FeedbackIcon from "../icons/Feedback.svg?react";
|
import FeedbackIcon from "../icons/Feedback.svg?react";
|
||||||
import { SelectInput } from "../input/SelectInput";
|
import { SelectInput } from "../input/SelectInput";
|
||||||
import {
|
|
||||||
useOptInAnalytics,
|
|
||||||
useDeveloperSettingsTab,
|
|
||||||
useShowConnectionStats,
|
|
||||||
isFirefox,
|
|
||||||
} from "./useSetting";
|
|
||||||
import { FieldRow, InputField } from "../input/Input";
|
import { FieldRow, InputField } from "../input/Input";
|
||||||
import { Body, Caption } from "../typography/Typography";
|
import { Body, Caption } from "../typography/Typography";
|
||||||
import { AnalyticsNotice } from "../analytics/AnalyticsNotice";
|
import { AnalyticsNotice } from "../analytics/AnalyticsNotice";
|
||||||
@ -46,6 +40,12 @@ import {
|
|||||||
useMediaDeviceNames,
|
useMediaDeviceNames,
|
||||||
} from "../livekit/MediaDevicesContext";
|
} from "../livekit/MediaDevicesContext";
|
||||||
import { widget } from "../widget";
|
import { widget } from "../widget";
|
||||||
|
import {
|
||||||
|
useSetting,
|
||||||
|
optInAnalytics as optInAnalyticsSetting,
|
||||||
|
developerSettingsTab as developerSettingsTabSetting,
|
||||||
|
isFirefox,
|
||||||
|
} from "./settings";
|
||||||
|
|
||||||
type SettingsTab =
|
type SettingsTab =
|
||||||
| "audio"
|
| "audio"
|
||||||
@ -76,11 +76,10 @@ export const SettingsModal: FC<Props> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [optInAnalytics, setOptInAnalytics] = useOptInAnalytics();
|
const [optInAnalytics, setOptInAnalytics] = useSetting(optInAnalyticsSetting);
|
||||||
const [developerSettingsTab, setDeveloperSettingsTab] =
|
const [developerSettingsTab, setDeveloperSettingsTab] = useSetting(
|
||||||
useDeveloperSettingsTab();
|
developerSettingsTabSetting,
|
||||||
const [showConnectionStats, setShowConnectionStats] =
|
);
|
||||||
useShowConnectionStats();
|
|
||||||
|
|
||||||
// Generate a `SelectInput` with a list of devices for a given device kind.
|
// Generate a `SelectInput` with a list of devices for a given device kind.
|
||||||
const generateDeviceSelection = (
|
const generateDeviceSelection = (
|
||||||
@ -245,18 +244,6 @@ export const SettingsModal: FC<Props> = ({
|
|||||||
})}
|
})}
|
||||||
</Body>
|
</Body>
|
||||||
</FieldRow>
|
</FieldRow>
|
||||||
<FieldRow>
|
|
||||||
<InputField
|
|
||||||
id="showConnectionStats"
|
|
||||||
name="connection-stats"
|
|
||||||
label={t("settings.show_connection_stats_label")}
|
|
||||||
type="checkbox"
|
|
||||||
checked={showConnectionStats}
|
|
||||||
onChange={(e: ChangeEvent<HTMLInputElement>): void =>
|
|
||||||
setShowConnectionStats(e.target.checked)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</FieldRow>
|
|
||||||
</TabItem>
|
</TabItem>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
98
src/settings/settings.ts
Normal file
98
src/settings/settings.ts
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2024 New Vector Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
import { BehaviorSubject, Observable } from "rxjs";
|
||||||
|
import { useObservableEagerState } from "observable-hooks";
|
||||||
|
|
||||||
|
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
|
||||||
|
|
||||||
|
export class Setting<T> {
|
||||||
|
public constructor(key: string, defaultValue: T) {
|
||||||
|
this.key = `matrix-setting-${key}`;
|
||||||
|
|
||||||
|
const storedValue = localStorage.getItem(this.key);
|
||||||
|
let initialValue = defaultValue;
|
||||||
|
if (storedValue !== null) {
|
||||||
|
try {
|
||||||
|
initialValue = JSON.parse(storedValue);
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn(`Invalid value stored for setting ${key}: ${storedValue}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._value = new BehaviorSubject(initialValue);
|
||||||
|
this.value = this._value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly key: string;
|
||||||
|
|
||||||
|
private readonly _value: BehaviorSubject<T>;
|
||||||
|
public readonly value: Observable<T>;
|
||||||
|
|
||||||
|
public readonly setValue = (value: T): void => {
|
||||||
|
this._value.next(value);
|
||||||
|
localStorage.setItem(this.key, JSON.stringify(value));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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];
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: This doesn't belong here
|
||||||
|
export const isFirefox = (): boolean => {
|
||||||
|
const { userAgent } = navigator;
|
||||||
|
return userAgent.includes("Firefox");
|
||||||
|
};
|
||||||
|
|
||||||
|
// null = undecided
|
||||||
|
export const optInAnalytics = new Setting<boolean | null>(
|
||||||
|
"opt-in-analytics",
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
// TODO: This setting can be disabled. Work out an approach to disableable
|
||||||
|
// settings thats works for Observables in addition to React.
|
||||||
|
export const useOptInAnalytics = (): [
|
||||||
|
boolean | null,
|
||||||
|
((value: boolean | null) => void) | null,
|
||||||
|
] => {
|
||||||
|
const setting = useSetting(optInAnalytics);
|
||||||
|
if (PosthogAnalytics.instance.isEnabled()) return setting;
|
||||||
|
|
||||||
|
return [false, null];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const developerSettingsTab = new Setting(
|
||||||
|
"developer-settings-tab",
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const audioInput = new Setting<string | undefined>(
|
||||||
|
"audio-input",
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
export const audioOutput = new Setting<string | undefined>(
|
||||||
|
"audio-output",
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
export const videoInput = new Setting<string | undefined>(
|
||||||
|
"video-input",
|
||||||
|
undefined,
|
||||||
|
);
|
@ -1,104 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2022 - 2023 New Vector Ltd
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { useCallback, useMemo } from "react";
|
|
||||||
|
|
||||||
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
|
|
||||||
import {
|
|
||||||
getLocalStorageItem,
|
|
||||||
setLocalStorageItem,
|
|
||||||
useLocalStorage,
|
|
||||||
} from "../useLocalStorage";
|
|
||||||
|
|
||||||
type Setting<T> = [T, (value: T) => void];
|
|
||||||
type DisableableSetting<T> = [T, ((value: T) => void) | null];
|
|
||||||
|
|
||||||
export const getSettingKey = (name: string): string => {
|
|
||||||
return `matrix-setting-${name}`;
|
|
||||||
};
|
|
||||||
// Like useState, but reads from and persists the value to localStorage
|
|
||||||
export const useSetting = <T>(name: string, defaultValue: T): Setting<T> => {
|
|
||||||
const key = useMemo(() => getSettingKey(name), [name]);
|
|
||||||
|
|
||||||
const [item, setItem] = useLocalStorage(key);
|
|
||||||
|
|
||||||
const value = useMemo(
|
|
||||||
() => (item == null ? defaultValue : JSON.parse(item)),
|
|
||||||
[item, defaultValue],
|
|
||||||
);
|
|
||||||
const setValue = useCallback(
|
|
||||||
(value: T) => {
|
|
||||||
setItem(JSON.stringify(value));
|
|
||||||
},
|
|
||||||
[setItem],
|
|
||||||
);
|
|
||||||
|
|
||||||
return [value, setValue];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getSetting = <T>(name: string, defaultValue: T): T => {
|
|
||||||
const item = getLocalStorageItem(getSettingKey(name));
|
|
||||||
return item === null ? defaultValue : JSON.parse(item);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const setSetting = <T>(name: string, newValue: T): void =>
|
|
||||||
setLocalStorageItem(getSettingKey(name), JSON.stringify(newValue));
|
|
||||||
|
|
||||||
export const isFirefox = (): boolean => {
|
|
||||||
const { userAgent } = navigator;
|
|
||||||
return userAgent.includes("Firefox");
|
|
||||||
};
|
|
||||||
|
|
||||||
const canEnableSpatialAudio = (): boolean => {
|
|
||||||
// Spatial audio means routing audio through audio contexts. On Chrome,
|
|
||||||
// this bypasses the AEC processor and so breaks echo cancellation.
|
|
||||||
// We only allow spatial audio to be enabled on Firefox which we know
|
|
||||||
// passes audio context audio through the AEC algorithm.
|
|
||||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=687574 is the
|
|
||||||
// chrome bug for this: once this is fixed and the updated version is deployed
|
|
||||||
// widely enough, we can allow spatial audio everywhere. It's currently in a
|
|
||||||
// chrome flag, so we could enable this in Electron if we enabled the chrome flag
|
|
||||||
// in the Electron wrapper.
|
|
||||||
return isFirefox();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useSpatialAudio = (): DisableableSetting<boolean> => {
|
|
||||||
const settingVal = useSetting("spatial-audio", false);
|
|
||||||
if (canEnableSpatialAudio()) return settingVal;
|
|
||||||
|
|
||||||
return [false, null];
|
|
||||||
};
|
|
||||||
|
|
||||||
// null = undecided
|
|
||||||
export const useOptInAnalytics = (): DisableableSetting<boolean | null> => {
|
|
||||||
const settingVal = useSetting<boolean | null>("opt-in-analytics", null);
|
|
||||||
if (PosthogAnalytics.instance.isEnabled()) return settingVal;
|
|
||||||
|
|
||||||
return [false, null];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useDeveloperSettingsTab = (): Setting<boolean> =>
|
|
||||||
useSetting("developer-settings-tab", false);
|
|
||||||
|
|
||||||
export const useShowConnectionStats = (): Setting<boolean> =>
|
|
||||||
useSetting("show-connection-stats", false);
|
|
||||||
|
|
||||||
export const useAudioInput = (): Setting<string | undefined> =>
|
|
||||||
useSetting<string | undefined>("audio-input", undefined);
|
|
||||||
export const useAudioOutput = (): Setting<string | undefined> =>
|
|
||||||
useSetting<string | undefined>("audio-output", undefined);
|
|
||||||
export const useVideoInput = (): Setting<string | undefined> =>
|
|
||||||
useSetting<string | undefined>("video-input", undefined);
|
|
@ -7104,6 +7104,11 @@ object.values@^1.1.7:
|
|||||||
define-properties "^1.2.0"
|
define-properties "^1.2.0"
|
||||||
es-abstract "^1.22.1"
|
es-abstract "^1.22.1"
|
||||||
|
|
||||||
|
observable-hooks@^4.2.3:
|
||||||
|
version "4.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/observable-hooks/-/observable-hooks-4.2.3.tgz#69e3353caafd7887ad9030bd440b053304e8d2d1"
|
||||||
|
integrity sha512-d6fYTIU+9sg1V+CT0GhgoE/ntjIqcy9DGaYGE6ELGVP4ojaWIEsaLvL/05hLOM+AL7aySN4DCTLvj6dDF9T8XA==
|
||||||
|
|
||||||
oidc-client-ts@^3.0.1:
|
oidc-client-ts@^3.0.1:
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/oidc-client-ts/-/oidc-client-ts-3.0.1.tgz#be264fb87c89f74f73863646431c32cd06f5ceb7"
|
resolved "https://registry.yarnpkg.com/oidc-client-ts/-/oidc-client-ts-3.0.1.tgz#be264fb87c89f74f73863646431c32cd06f5ceb7"
|
||||||
|
Loading…
Reference in New Issue
Block a user