= ({
+ {settingsModalState.isOpen && (
+
+ )}
{inviteModalState.isOpen && showControls && (
)}
diff --git a/src/room/RoomPage.tsx b/src/room/RoomPage.tsx
index fe91861..21ebc63 100644
--- a/src/room/RoomPage.tsx
+++ b/src/room/RoomPage.tsx
@@ -1,5 +1,5 @@
/*
-Copyright 2021-2022 New Vector Ltd
+Copyright 2021-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.
@@ -24,7 +24,6 @@ import { RoomAuthView } from "./RoomAuthView";
import { GroupCallLoader } from "./GroupCallLoader";
import { GroupCallView } from "./GroupCallView";
import { useUrlParams } from "../UrlParams";
-import { MediaHandlerProvider } from "../settings/useMediaHandler";
import { useRegisterPasswordlessUser } from "../auth/useRegisterPasswordlessUser";
import { translatedError } from "../TranslatedError";
import { useOptInAnalytics } from "../settings/useSetting";
@@ -101,15 +100,13 @@ export const RoomPage: FC = () => {
}
return (
-
-
- {groupCallView}
-
-
+
+ {groupCallView}
+
);
};
diff --git a/src/room/VideoPreview.tsx b/src/room/VideoPreview.tsx
index 0e92cc5..a7fc498 100644
--- a/src/room/VideoPreview.tsx
+++ b/src/room/VideoPreview.tsx
@@ -1,5 +1,5 @@
/*
-Copyright 2022 New Vector Ltd
+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.
@@ -14,21 +14,22 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React from "react";
+import React, { useCallback } from "react";
import useMeasure from "react-use-measure";
import { ResizeObserver } from "@juggle/resize-observer";
import { GroupCallState } from "matrix-js-sdk/src/webrtc/groupCall";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { useTranslation } from "react-i18next";
+import { OverlayTriggerState } from "@react-stately/overlays";
-import { MicButton, VideoButton } from "../button";
+import { MicButton, SettingsButton, VideoButton } from "../button";
import { useMediaStream } from "../video-grid/useMediaStream";
-import { OverflowMenu } from "./OverflowMenu";
import { Avatar } from "../Avatar";
import { useProfile } from "../profile/useProfile";
import styles from "./VideoPreview.module.css";
import { Body } from "../typography/Typography";
import { useModalTriggerState } from "../Modal";
+import { SettingsModal } from "../settings/SettingsModal";
interface Props {
client: MatrixClient;
@@ -59,8 +60,20 @@ export function VideoPreview({
const [previewRef, previewBounds] = useMeasure({ polyfill: ResizeObserver });
const avatarSize = (previewBounds.height - 66) / 2;
- const { modalState: feedbackModalState, modalProps: feedbackModalProps } =
- useModalTriggerState();
+ const {
+ modalState: settingsModalState,
+ modalProps: settingsModalProps,
+ }: {
+ modalState: OverlayTriggerState;
+ modalProps: {
+ isOpen: boolean;
+ onClose: () => void;
+ };
+ } = useModalTriggerState();
+
+ const openSettings = useCallback(() => {
+ settingsModalState.open();
+ }, [settingsModalState]);
return (
@@ -95,17 +108,13 @@ export function VideoPreview({
muted={localVideoMuted}
onPress={toggleLocalVideoMuted}
/>
-
+
>
)}
+ {settingsModalState.isOpen && (
+
+ )}
);
}
diff --git a/src/settings/FeedbackSettingsTab.tsx b/src/settings/FeedbackSettingsTab.tsx
new file mode 100644
index 0000000..1383490
--- /dev/null
+++ b/src/settings/FeedbackSettingsTab.tsx
@@ -0,0 +1,93 @@
+/*
+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 React, { useCallback } from "react";
+import { randomString } from "matrix-js-sdk/src/randomstring";
+import { useTranslation } from "react-i18next";
+
+import { Button } from "../button";
+import { FieldRow, InputField, ErrorMessage } from "../input/Input";
+import { useSubmitRageshake, useRageshakeRequest } from "./submit-rageshake";
+import { Body } from "../typography/Typography";
+
+interface Props {
+ roomId?: string;
+}
+
+export function FeedbackSettingsTab({ roomId }: Props) {
+ const { t } = useTranslation();
+ const { submitRageshake, sending, error } = useSubmitRageshake();
+ const sendRageshakeRequest = useRageshakeRequest();
+
+ const onSubmitFeedback = useCallback(
+ (e) => {
+ e.preventDefault();
+ const data = new FormData(e.target);
+ const descriptionData = data.get("description");
+ const description =
+ typeof descriptionData === "string" ? descriptionData : "";
+ const sendLogs = Boolean(data.get("sendLogs"));
+ const rageshakeRequestId = randomString(16);
+
+ submitRageshake({
+ description,
+ sendLogs,
+ rageshakeRequestId,
+ roomId,
+ });
+
+ if (roomId && sendLogs) {
+ sendRageshakeRequest(roomId, rageshakeRequestId);
+ }
+ },
+ [submitRageshake, roomId, sendRageshakeRequest]
+ );
+
+ return (
+
+ {t("Having trouble? Help us fix it.")}
+
+
+ );
+}
diff --git a/src/profile/ProfileModal.module.css b/src/settings/ProfileSettingsTab.module.css
similarity index 93%
rename from src/profile/ProfileModal.module.css
rename to src/settings/ProfileSettingsTab.module.css
index 268fa93..4b80e49 100644
--- a/src/profile/ProfileModal.module.css
+++ b/src/settings/ProfileSettingsTab.module.css
@@ -1,5 +1,5 @@
/*
-Copyright 2022 New Vector Ltd
+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.
diff --git a/src/profile/ProfileModal.tsx b/src/settings/ProfileSettingsTab.tsx
similarity index 50%
rename from src/profile/ProfileModal.tsx
rename to src/settings/ProfileSettingsTab.tsx
index adb1e2b..3afd753 100644
--- a/src/profile/ProfileModal.tsx
+++ b/src/settings/ProfileSettingsTab.tsx
@@ -1,5 +1,5 @@
/*
-Copyright 2022 New Vector Ltd
+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.
@@ -14,27 +14,22 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React, { ChangeEvent, useCallback, useEffect, useState } from "react";
+import React, { ChangeEvent, useCallback, useState } from "react";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { useTranslation } from "react-i18next";
import { Button } from "../button";
-import { useProfile } from "./useProfile";
+import { useProfile } from "../profile/useProfile";
import { FieldRow, InputField, ErrorMessage } from "../input/Input";
-import { Modal, ModalContent } from "../Modal";
import { AvatarInputField } from "../input/AvatarInputField";
-import styles from "./ProfileModal.module.css";
+import styles from "./ProfileSettingsTab.module.css";
interface Props {
client: MatrixClient;
- onClose: () => void;
- [rest: string]: unknown;
}
-export function ProfileModal({ client, ...rest }: Props) {
- const { onClose } = rest;
+export function ProfileSettingsTab({ client }: Props) {
const { t } = useTranslation();
const {
- success,
error,
loading,
displayName: initialDisplayName,
@@ -78,64 +73,51 @@ export function ProfileModal({ client, ...rest }: Props) {
[saveProfile, removeAvatar]
);
- useEffect(() => {
- if (success) {
- onClose();
- }
- }, [success, onClose]);
-
return (
-
-
-
-
-
+
);
}
diff --git a/src/settings/SettingsModal.tsx b/src/settings/SettingsModal.tsx
index b126bb8..d2b5996 100644
--- a/src/settings/SettingsModal.tsx
+++ b/src/settings/SettingsModal.tsx
@@ -1,5 +1,5 @@
/*
-Copyright 2022 New Vector Ltd
+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.
@@ -14,9 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React from "react";
+import React, { useCallback, useState } from "react";
import { Item } from "@react-stately/collections";
import { Trans, useTranslation } from "react-i18next";
+import { MatrixClient } from "matrix-js-sdk";
import { Modal } from "../Modal";
import styles from "./SettingsModal.module.css";
@@ -25,6 +26,8 @@ import { ReactComponent as AudioIcon } from "../icons/Audio.svg";
import { ReactComponent as VideoIcon } from "../icons/Video.svg";
import { ReactComponent as DeveloperIcon } from "../icons/Developer.svg";
import { ReactComponent as OverflowIcon } from "../icons/Overflow.svg";
+import { ReactComponent as UserIcon } from "../icons/User.svg";
+import { ReactComponent as FeedbackIcon } from "../icons/Feedback.svg";
import { SelectInput } from "../input/SelectInput";
import { useMediaHandler } from "./useMediaHandler";
import {
@@ -39,9 +42,14 @@ import { Button } from "../button";
import { useDownloadDebugLog } from "./submit-rageshake";
import { Body, Caption } from "../typography/Typography";
import { AnalyticsNotice } from "../analytics/AnalyticsNotice";
+import { ProfileSettingsTab } from "./ProfileSettingsTab";
+import { FeedbackSettingsTab } from "./FeedbackSettingsTab";
interface Props {
isOpen: boolean;
+ client: MatrixClient;
+ roomId?: string;
+ defaultTab?: string;
onClose: () => void;
}
@@ -68,6 +76,15 @@ export const SettingsModal = (props: Props) => {
const downloadDebugLog = useDownloadDebugLog();
+ const [selectedTab, setSelectedTab] = useState();
+
+ const onSelectedTabChanged = useCallback(
+ (tab) => {
+ setSelectedTab(tab);
+ },
+ [setSelectedTab]
+ );
+
const optInDescription = (
@@ -87,8 +104,13 @@ export const SettingsModal = (props: Props) => {
className={styles.settingsModal}
{...props}
>
-
+
@@ -145,6 +167,7 @@ export const SettingsModal = (props: Props) => {
@@ -167,6 +190,29 @@ export const SettingsModal = (props: Props) => {
+
+ {t("Profile")}
+ >
+ }
+ >
+
+
+
+
+ {t("feedback")}
+ >
+ }
+ >
+
+
+
@@ -174,18 +220,10 @@ export const SettingsModal = (props: Props) => {
>
}
>
- Analytics
-
- ) =>
- setOptInAnalytics(event.target.checked)
- }
- />
-
+ Developer
+
+ Version: {(import.meta.env.VITE_APP_VERSION as string) || "dev"}
+
{
}
/>
+ Analytics
+
+ ) =>
+ setOptInAnalytics(event.target.checked)
+ }
+ />
+
{developerSettingsTab && (
diff --git a/src/settings/useMediaHandler.tsx b/src/settings/useMediaHandler.tsx
index ce3953e..6f491c8 100644
--- a/src/settings/useMediaHandler.tsx
+++ b/src/settings/useMediaHandler.tsx
@@ -1,5 +1,5 @@
/*
-Copyright 2022 New Vector Ltd
+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.
@@ -15,23 +15,7 @@ limitations under the License.
*/
/* eslint-disable @typescript-eslint/ban-ts-comment */
-/*
-Copyright 2022 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 { MatrixClient } from "matrix-js-sdk/src/client";
import { MediaHandlerEvent } from "matrix-js-sdk/src/webrtc/mediaHandler";
import React, {
useState,
@@ -43,6 +27,8 @@ import React, {
ReactNode,
} from "react";
+import { useClient } from "../ClientContext";
+
export interface MediaHandlerContextInterface {
audioInput: string;
audioInputs: MediaDeviceInfo[];
@@ -89,10 +75,10 @@ function updateMediaPreferences(newPreferences: MediaPreferences): void {
);
}
interface Props {
- client: MatrixClient;
children: ReactNode;
}
-export function MediaHandlerProvider({ client, children }: Props): JSX.Element {
+export function MediaHandlerProvider({ children }: Props): JSX.Element {
+ const { client } = useClient();
const [
{
audioInput,
@@ -104,19 +90,21 @@ export function MediaHandlerProvider({ client, children }: Props): JSX.Element {
},
setState,
] = useState(() => {
- const mediaPreferences = getMediaPreferences();
- const mediaHandler = client.getMediaHandler();
+ const mediaHandler = client?.getMediaHandler();
- mediaHandler.restoreMediaSettings(
- mediaPreferences?.audioInput,
- mediaPreferences?.videoInput
- );
+ if (mediaHandler) {
+ const mediaPreferences = getMediaPreferences();
+ mediaHandler?.restoreMediaSettings(
+ mediaPreferences?.audioInput,
+ mediaPreferences?.videoInput
+ );
+ }
return {
// @ts-ignore, ignore that audioInput is a private members of mediaHandler
- audioInput: mediaHandler.audioInput,
+ audioInput: mediaHandler?.audioInput,
// @ts-ignore, ignore that videoInput is a private members of mediaHandler
- videoInput: mediaHandler.videoInput,
+ videoInput: mediaHandler?.videoInput,
audioOutput: undefined,
audioInputs: [],
videoInputs: [],
@@ -125,6 +113,8 @@ export function MediaHandlerProvider({ client, children }: Props): JSX.Element {
});
useEffect(() => {
+ if (!client) return;
+
const mediaHandler = client.getMediaHandler();
function updateDevices(): void {