From 0269753f59f14b48835efa49dd5d4a567b17f98c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 5 May 2023 11:44:35 +0200 Subject: [PATCH 01/15] Settings improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/App.tsx | 57 +++---- src/UserMenu.tsx | 8 +- src/UserMenuContainer.tsx | 23 ++- src/button/Button.tsx | 6 +- src/room/FeedbackModal.tsx | 114 -------------- src/room/InCallView.tsx | 76 ++++++---- src/room/OverflowMenu.tsx | 143 ------------------ src/room/PTTCallView.tsx | 27 ++-- src/room/RoomPage.tsx | 21 ++- src/room/VideoPreview.tsx | 37 +++-- src/settings/FeedbackSettingsTab.tsx | 93 ++++++++++++ .../ProfileSettingsTab.module.css} | 2 +- .../ProfileSettingsTab.tsx} | 118 ++++++--------- src/settings/SettingsModal.tsx | 81 ++++++++-- src/settings/useMediaHandler.tsx | 44 +++--- 15 files changed, 380 insertions(+), 470 deletions(-) delete mode 100644 src/room/FeedbackModal.tsx delete mode 100644 src/room/OverflowMenu.tsx create mode 100644 src/settings/FeedbackSettingsTab.tsx rename src/{profile/ProfileModal.module.css => settings/ProfileSettingsTab.module.css} (93%) rename src/{profile/ProfileModal.tsx => settings/ProfileSettingsTab.tsx} (50%) diff --git a/src/App.tsx b/src/App.tsx index 5fc0414..2ae27b7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,5 @@ /* -Copyright 2021 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. @@ -30,6 +30,7 @@ import { SequenceDiagramViewerPage } from "./SequenceDiagramViewerPage"; import { InspectorContextProvider } from "./room/GroupCallInspector"; import { CrashView, LoadingView } from "./FullScreenView"; import { Initializer } from "./initializer"; +import { MediaHandlerProvider } from "./settings/useMediaHandler"; const SentryRoute = Sentry.withSentryRouting(Route); @@ -55,32 +56,34 @@ export default function App({ history }: AppProps) { {loaded ? ( - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) : ( diff --git a/src/UserMenu.tsx b/src/UserMenu.tsx index 36cad0d..0bf2a79 100644 --- a/src/UserMenu.tsx +++ b/src/UserMenu.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. @@ -25,6 +25,7 @@ import { Menu } from "./Menu"; import { TooltipTrigger } from "./Tooltip"; import { Avatar, Size } from "./Avatar"; import { ReactComponent as UserIcon } from "./icons/User.svg"; +import { ReactComponent as SettingsIcon } from "./icons/Settings.svg"; import { ReactComponent as LoginIcon } from "./icons/Login.svg"; import { ReactComponent as LogoutIcon } from "./icons/Logout.svg"; import { Body } from "./typography/Typography"; @@ -59,6 +60,11 @@ export function UserMenu({ icon: UserIcon, label: displayName, }); + arr.push({ + key: "settings", + icon: SettingsIcon, + label: t("Settings"), + }); if (isPasswordlessUser && !preventNavigation) { arr.push({ diff --git a/src/UserMenuContainer.tsx b/src/UserMenuContainer.tsx index e2c57ca..0a116d9 100644 --- a/src/UserMenuContainer.tsx +++ b/src/UserMenuContainer.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,13 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { useCallback } from "react"; +import React, { useCallback, useState } from "react"; import { useHistory, useLocation } from "react-router-dom"; import { useClient } from "./ClientContext"; import { useProfile } from "./profile/useProfile"; import { useModalTriggerState } from "./Modal"; -import { ProfileModal } from "./profile/ProfileModal"; +import { SettingsModal } from "./settings/SettingsModal"; import { UserMenu } from "./UserMenu"; interface Props { @@ -35,10 +35,17 @@ export function UserMenuContainer({ preventNavigation = false }: Props) { const { displayName, avatarUrl } = useProfile(client); const { modalState, modalProps } = useModalTriggerState(); + const [defaultSettingsTab, setDefaultSettingsTab] = useState(); + const onAction = useCallback( - (value: string) => { + async (value: string) => { switch (value) { case "user": + setDefaultSettingsTab("profile"); + modalState.open(); + break; + case "settings": + setDefaultSettingsTab("audio"); modalState.open(); break; case "logout": @@ -64,7 +71,13 @@ export function UserMenuContainer({ preventNavigation = false }: Props) { displayName || (userName ? userName.replace("@", "") : undefined) } /> - {modalState.isOpen && } + {modalState.isOpen && ( + + )} ); } diff --git a/src/button/Button.tsx b/src/button/Button.tsx index 78b8299..1ac7921 100644 --- a/src/button/Button.tsx +++ b/src/button/Button.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. @@ -246,9 +246,11 @@ export function SettingsButton({ export function InviteButton({ className, + variant = "toolbar", ...rest }: { className?: string; + variant?: string; // TODO: add all props for diff --git a/src/room/FeedbackModal.tsx b/src/room/FeedbackModal.tsx deleted file mode 100644 index 537132c..0000000 --- a/src/room/FeedbackModal.tsx +++ /dev/null @@ -1,114 +0,0 @@ -/* -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 React, { useCallback, useEffect } from "react"; -import { randomString } from "matrix-js-sdk/src/randomstring"; -import { useTranslation } from "react-i18next"; - -import { Modal, ModalContent } from "../Modal"; -import { Button } from "../button"; -import { FieldRow, InputField, ErrorMessage } from "../input/Input"; -import { - useSubmitRageshake, - useRageshakeRequest, -} from "../settings/submit-rageshake"; -import { Body } from "../typography/Typography"; - -interface Props { - inCall: boolean; - roomId: string; - onClose?: () => void; - // TODO: add all props for for - [index: string]: unknown; -} - -export function FeedbackModal({ inCall, roomId, onClose, ...rest }: Props) { - const { t } = useTranslation(); - const { submitRageshake, sending, sent, 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 (inCall && sendLogs) { - sendRageshakeRequest(roomId, rageshakeRequestId); - } - }, - [inCall, submitRageshake, roomId, sendRageshakeRequest] - ); - - useEffect(() => { - if (sent) { - onClose(); - } - }, [sent, onClose]); - - return ( - - - {t("Having trouble? Help us fix it.")} -
- - - - - - - {error && ( - - - - )} - - - -
-
-
- ); -} diff --git a/src/room/InCallView.tsx b/src/room/InCallView.tsx index b3fdaa3..41658c0 100644 --- a/src/room/InCallView.tsx +++ b/src/room/InCallView.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. @@ -24,6 +24,7 @@ import { GroupCall } from "matrix-js-sdk/src/webrtc/groupCall"; import { CallFeed } from "matrix-js-sdk/src/webrtc/callFeed"; import classNames from "classnames"; import { useTranslation } from "react-i18next"; +import { OverlayTriggerState } from "@react-stately/overlays"; import { JoinRule } from "matrix-js-sdk/src/@types/partials"; import type { IWidgetApiRequest } from "matrix-widget-api"; @@ -33,6 +34,8 @@ import { MicButton, VideoButton, ScreenshareButton, + SettingsButton, + InviteButton, } from "../button"; import { Header, @@ -48,12 +51,8 @@ import { } from "../video-grid/VideoGrid"; import { VideoTileContainer } from "../video-grid/VideoTileContainer"; import { GroupCallInspector } from "./GroupCallInspector"; -import { OverflowMenu } from "./OverflowMenu"; import { GridLayoutMenu } from "./GridLayoutMenu"; import { Avatar } from "../Avatar"; -import { UserMenuContainer } from "../UserMenuContainer"; -import { useRageshakeRequestModal } from "../settings/submit-rageshake"; -import { RageshakeRequestModal } from "./RageshakeRequestModal"; import { useMediaHandler } from "../settings/useMediaHandler"; import { useNewGrid, @@ -74,6 +73,8 @@ import { AudioSink } from "../video-grid/AudioSink"; import { useCallViewKeyboardShortcuts } from "../useCallViewKeyboardShortcuts"; import { NewVideoGrid } from "../video-grid/NewVideoGrid"; import { OTelGroupCallMembership } from "../otel/OTelGroupCallMembership"; +import { SettingsModal } from "../settings/SettingsModal"; +import { InviteModal } from "./InviteModal"; const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {}); // There is currently a bug in Safari our our code with cloning and sending MediaStreams @@ -128,7 +129,6 @@ export function InCallView({ }: Props) { const { t } = useTranslation(); usePreventScroll(); - const joinRule = useJoinRule(groupCall.room); const containerRef1 = useRef(null); const [containerRef2, bounds] = useMeasure({ polyfill: ResizeObserver }); @@ -151,11 +151,10 @@ export function InCallView({ const [audioContext, audioDestination] = useAudioContext(); const [showInspector] = useShowInspector(); - const { modalState: feedbackModalState, modalProps: feedbackModalProps } = - useModalTriggerState(); - const { hideScreensharing } = useUrlParams(); + const joinRule = useJoinRule(groupCall.room); + useCallViewKeyboardShortcuts( containerRef1, toggleMicrophoneMuted, @@ -342,9 +341,34 @@ export function InCallView({ }; const { - modalState: rageshakeRequestModalState, - modalProps: rageshakeRequestModalProps, - } = useRageshakeRequestModal(groupCall.room.roomId); + modalState: settingsModalState, + modalProps: settingsModalProps, + }: { + modalState: OverlayTriggerState; + modalProps: { + isOpen: boolean; + onClose: () => void; + }; + } = useModalTriggerState(); + + const openSettings = useCallback(() => { + settingsModalState.open(); + }, [settingsModalState]); + + const { + modalState: inviteModalState, + modalProps: inviteModalProps, + }: { + modalState: OverlayTriggerState; + modalProps: { + isOpen: boolean; + onClose: () => void; + }; + } = useModalTriggerState(); + + const openInvite = useCallback(() => { + inviteModalState.open(); + }, [inviteModalState]); const containerClasses = classNames(styles.inRoom, { [styles.maximised]: maximisedParticipant, @@ -402,17 +426,7 @@ export function InCallView({ ); } if (!maximisedParticipant) { - buttons.push( - - ); + buttons.push(); } } @@ -434,7 +448,9 @@ export function InCallView({ - + {joinRule === JoinRule.Public && ( + + )} )} @@ -448,12 +464,16 @@ export function InCallView({ otelGroupCallMembership={otelGroupCallMembership} show={showInspector} /> - {rageshakeRequestModalState.isOpen && ( - )} + {inviteModalState.isOpen && ( + + )} ); } diff --git a/src/room/OverflowMenu.tsx b/src/room/OverflowMenu.tsx deleted file mode 100644 index 0920f2d..0000000 --- a/src/room/OverflowMenu.tsx +++ /dev/null @@ -1,143 +0,0 @@ -/* -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 React, { useCallback } from "react"; -import { Item } from "@react-stately/collections"; -import { GroupCall } from "matrix-js-sdk/src/webrtc/groupCall"; -import { OverlayTriggerState } from "@react-stately/overlays"; -import { useTranslation } from "react-i18next"; - -import { Button } from "../button"; -import { Menu } from "../Menu"; -import { PopoverMenuTrigger } from "../popover/PopoverMenu"; -import { ReactComponent as SettingsIcon } from "../icons/Settings.svg"; -import { ReactComponent as AddUserIcon } from "../icons/AddUser.svg"; -import { ReactComponent as OverflowIcon } from "../icons/Overflow.svg"; -import { ReactComponent as FeedbackIcon } from "../icons/Feedback.svg"; -import { useModalTriggerState } from "../Modal"; -import { SettingsModal } from "../settings/SettingsModal"; -import { InviteModal } from "./InviteModal"; -import { TooltipTrigger } from "../Tooltip"; -import { FeedbackModal } from "./FeedbackModal"; -import { Config } from "../config/Config"; - -interface Props { - roomIdOrAlias: string; - inCall: boolean; - groupCall: GroupCall; - showInvite: boolean; - feedbackModalState: OverlayTriggerState; - feedbackModalProps: { - isOpen: boolean; - onClose: () => void; - }; -} - -export function OverflowMenu({ - roomIdOrAlias, - inCall, - groupCall, - showInvite, - feedbackModalState, - feedbackModalProps, -}: Props) { - const { t } = useTranslation(); - - const { - modalState: inviteModalState, - modalProps: inviteModalProps, - }: { - modalState: OverlayTriggerState; - modalProps: { - isOpen: boolean; - onClose: () => void; - }; - } = useModalTriggerState(); - const { - modalState: settingsModalState, - modalProps: settingsModalProps, - }: { - modalState: OverlayTriggerState; - modalProps: { - isOpen: boolean; - onClose: () => void; - }; - } = useModalTriggerState(); - - // TODO: On closing modal, focus should be restored to the trigger button - // https://github.com/adobe/react-spectrum/issues/2444 - const onAction = useCallback( - (key) => { - switch (key) { - case "invite": - inviteModalState.open(); - break; - case "settings": - settingsModalState.open(); - break; - case "feedback": - feedbackModalState.open(); - break; - } - }, - [feedbackModalState, inviteModalState, settingsModalState] - ); - - const tooltip = useCallback(() => t("More"), [t]); - - return ( - <> - - - - - {(props: JSX.IntrinsicAttributes) => ( - - {showInvite && ( - - - {t("Invite people")} - - )} - - - {t("Settings")} - - {Config.get().rageshake?.submit_url && ( - - - {t("Submit feedback")} - - )} - - )} - - {settingsModalState.isOpen && } - {inviteModalState.isOpen && ( - - )} - {feedbackModalState.isOpen && ( - - )} - - ); -} diff --git a/src/room/PTTCallView.tsx b/src/room/PTTCallView.tsx index 2781640..af5437d 100644 --- a/src/room/PTTCallView.tsx +++ b/src/room/PTTCallView.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. @@ -27,7 +27,7 @@ import { useTranslation } from "react-i18next"; import { useDelayedState } from "../useDelayedState"; import { useModalTriggerState } from "../Modal"; import { InviteModal } from "./InviteModal"; -import { HangupButton, InviteButton } from "../button"; +import { HangupButton, InviteButton, SettingsButton } from "../button"; import { Header, LeftNav, RightNav, RoomSetupHeaderInfo } from "../Header"; import styles from "./PTTCallView.module.css"; import { Facepile } from "../Facepile"; @@ -41,10 +41,10 @@ import { ReactComponent as AudioIcon } from "../icons/Audio.svg"; import { usePTTSounds } from "../sound/usePttSounds"; import { PTTClips } from "../sound/PTTClips"; import { GroupCallInspector } from "./GroupCallInspector"; -import { OverflowMenu } from "./OverflowMenu"; import { Size } from "../Avatar"; import { ParticipantInfo } from "./useGroupCall"; import { OTelGroupCallMembership } from "../otel/OTelGroupCallMembership"; +import { SettingsModal } from "../settings/SettingsModal"; function getPromptText( networkWaiting: boolean, @@ -126,8 +126,9 @@ export const PTTCallView: React.FC = ({ const { t } = useTranslation(); const { modalState: inviteModalState, modalProps: inviteModalProps } = useModalTriggerState(); - const { modalState: feedbackModalState, modalProps: feedbackModalProps } = + const { modalState: settingsModalState, modalProps: settingsModalProps } = useModalTriggerState(); + const [containerRef, bounds] = useMeasure({ polyfill: ResizeObserver }); const facepileSize = bounds.width < 800 ? Size.SM : Size.MD; const showControls = bounds.height > 500; @@ -232,14 +233,7 @@ export const PTTCallView: React.FC = ({ />
- + settingsModalState.open()} /> {!isEmbedded && } inviteModalState.open()} />
@@ -265,7 +259,7 @@ export const PTTCallView: React.FC = ({
))} = ({
+ {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.")} +
+ + + + + + + {error && ( + + + + )} + + + +
+
+ ); +} 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 ( - - -
- - - - - - - - - - {error && ( - - - - )} - - - - -
-
-
+
+ + + + + + + + + + {error && ( + + + + )} + + + +
); } 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 { From f11e1fac6bbe7db9d9260c0418f2d543ce2a41e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 5 May 2023 12:04:48 +0200 Subject: [PATCH 02/15] i18n MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- public/locales/en-GB/app.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/locales/en-GB/app.json b/public/locales/en-GB/app.json index 6e91a53..13e29a8 100644 --- a/public/locales/en-GB/app.json +++ b/public/locales/en-GB/app.json @@ -47,6 +47,7 @@ "Element Call Home": "Element Call Home", "Exit full screen": "Exit full screen", "Expose developer settings in the settings window.": "Expose developer settings in the settings window.", + "feedback": "feedback", "Fetching group call timed out.": "Fetching group call timed out.", "Freedom": "Freedom", "Full screen": "Full screen", @@ -74,7 +75,6 @@ "Microphone {{n}}": "Microphone {{n}}", "Microphone permissions needed to join the call.": "Microphone permissions needed to join the call.", "More": "More", - "More menu": "More menu", "Mute microphone": "Mute microphone", "No": "No", "Not now, return to home screen": "Not now, return to home screen", From d4f0300c82fa3f1fa5c9f2013287f764656af9f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 5 May 2023 19:17:49 +0200 Subject: [PATCH 03/15] Match designs Co-authored-by: Robin --- src/settings/FeedbackSettingsTab.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings/FeedbackSettingsTab.tsx b/src/settings/FeedbackSettingsTab.tsx index 1383490..79272ec 100644 --- a/src/settings/FeedbackSettingsTab.tsx +++ b/src/settings/FeedbackSettingsTab.tsx @@ -84,7 +84,7 @@ export function FeedbackSettingsTab({ roomId }: Props) { )} From 93a47e7009d4a02d8f52e96ba44a29d183881cbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 5 May 2023 19:17:59 +0200 Subject: [PATCH 04/15] Fix casing Co-authored-by: Robin --- src/settings/SettingsModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings/SettingsModal.tsx b/src/settings/SettingsModal.tsx index d2b5996..9fec9de 100644 --- a/src/settings/SettingsModal.tsx +++ b/src/settings/SettingsModal.tsx @@ -205,7 +205,7 @@ export const SettingsModal = (props: Props) => { title={ <> - {t("feedback")} + {t("Feedback")} } > From 57e79862a5608f4d01f1a395a0b7470109ae6539 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 5 May 2023 19:18:34 +0200 Subject: [PATCH 05/15] User ID -> Username Co-authored-by: Robin --- src/settings/ProfileSettingsTab.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings/ProfileSettingsTab.tsx b/src/settings/ProfileSettingsTab.tsx index 3afd753..07fc8e4 100644 --- a/src/settings/ProfileSettingsTab.tsx +++ b/src/settings/ProfileSettingsTab.tsx @@ -89,7 +89,7 @@ export function ProfileSettingsTab({ client }: Props) { Date: Fri, 5 May 2023 19:29:11 +0200 Subject: [PATCH 06/15] Feedback copy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/settings/FeedbackSettingsTab.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/settings/FeedbackSettingsTab.tsx b/src/settings/FeedbackSettingsTab.tsx index 79272ec..53ce576 100644 --- a/src/settings/FeedbackSettingsTab.tsx +++ b/src/settings/FeedbackSettingsTab.tsx @@ -22,6 +22,7 @@ import { Button } from "../button"; import { FieldRow, InputField, ErrorMessage } from "../input/Input"; import { useSubmitRageshake, useRageshakeRequest } from "./submit-rageshake"; import { Body } from "../typography/Typography"; +import styles from "../input/SelectInput.module.css"; interface Props { roomId?: string; @@ -58,13 +59,18 @@ export function FeedbackSettingsTab({ roomId }: Props) { return (
- {t("Having trouble? Help us fix it.")} +

{t("Submit feedback")}

+ + {t( + "If you are experiencing issues or simply would like to provide some feedback, please send us a short description below." + )} +
From 6cad89b20ca71e556e566cb8240f372701094f6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 5 May 2023 19:36:23 +0200 Subject: [PATCH 07/15] Add success message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/settings/FeedbackSettingsTab.tsx | 37 +++++++++++++++------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/settings/FeedbackSettingsTab.tsx b/src/settings/FeedbackSettingsTab.tsx index 53ce576..da40678 100644 --- a/src/settings/FeedbackSettingsTab.tsx +++ b/src/settings/FeedbackSettingsTab.tsx @@ -30,7 +30,7 @@ interface Props { export function FeedbackSettingsTab({ roomId }: Props) { const { t } = useTranslation(); - const { submitRageshake, sending, error } = useSubmitRageshake(); + const { submitRageshake, sending, sent, error } = useSubmitRageshake(); const sendRageshakeRequest = useRageshakeRequest(); const onSubmitFeedback = useCallback( @@ -72,27 +72,30 @@ export function FeedbackSettingsTab({ roomId }: Props) { name="description" label={t("Your feedback")} type="textarea" + disabled={sending || sent} /> - - - - {error && ( + {sent ? ( + {t("Thanks, we received your feedback!")} + ) : ( - + + {error && ( + + + + )} + )} - - -
); From cf1a7f2e21b915e5e3f2603e16edb12b111cd436 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Mon, 22 May 2023 12:54:26 -0400 Subject: [PATCH 08/15] Match settings modal to design nuances better --- src/Modal.module.css | 4 ++-- src/button/Button.module.css | 4 ++-- src/index.css | 8 +++++++- src/input/SelectInput.module.css | 2 -- src/settings/SettingsModal.module.css | 13 ++++--------- src/settings/SettingsModal.tsx | 2 +- src/tabs/Tabs.module.css | 13 +++++++++---- 7 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/Modal.module.css b/src/Modal.module.css index 30c8af0..5143f8b 100644 --- a/src/Modal.module.css +++ b/src/Modal.module.css @@ -40,7 +40,7 @@ limitations under the License. .modalHeader { display: flex; justify-content: space-between; - padding: 34px 34px 0 34px; + padding: 34px 32px 0 32px; } .modalHeader h3 { @@ -72,7 +72,7 @@ limitations under the License. .modalHeader { display: flex; justify-content: space-between; - padding: 24px 24px 0 24px; + padding: 32px 20px 0 20px; } .modal.mobileFullScreen { diff --git a/src/button/Button.module.css b/src/button/Button.module.css index eb8f0b1..b19db46 100644 --- a/src/button/Button.module.css +++ b/src/button/Button.module.css @@ -39,10 +39,10 @@ limitations under the License. .secondaryHangup, .button, .copyButton { - padding: 7px 15px; + padding: 8px 20px; border-radius: 8px; font-size: var(--font-size-body); - font-weight: 700; + font-weight: 600; } .button { diff --git a/src/index.css b/src/index.css index fbc9d1a..f9e00ef 100644 --- a/src/index.css +++ b/src/index.css @@ -180,10 +180,16 @@ h2 { /* Subtitle */ h3 { - font-weight: 400; + font-weight: 600; font-size: var(--font-size-subtitle); } +/* Body Semi Bold */ +h4 { + font-weight: 600; + font-size: var(--font-size-body); +} + h1, h2, h3 { diff --git a/src/input/SelectInput.module.css b/src/input/SelectInput.module.css index 727ede0..086be82 100644 --- a/src/input/SelectInput.module.css +++ b/src/input/SelectInput.module.css @@ -22,8 +22,6 @@ limitations under the License. } .label { - font-weight: 600; - font-size: var(--font-size-subtitle); margin-top: 0; margin-bottom: 12px; } diff --git a/src/settings/SettingsModal.module.css b/src/settings/SettingsModal.module.css index 1e44dad..864d795 100644 --- a/src/settings/SettingsModal.module.css +++ b/src/settings/SettingsModal.module.css @@ -19,6 +19,10 @@ limitations under the License. height: 480px; } +.settingsModal p { + color: var(--secondary-content); +} + .tabContainer { padding: 27px 20px; } @@ -26,12 +30,3 @@ limitations under the License. .fieldRowText { margin-bottom: 0; } - -/* -This style guarantees a fixed width of the tab bar in the settings window. -The "Developer" item in the tab bar can be toggled. -Without a defined width activating the developer tab makes the tab container jump to the right. -*/ -.tabLabel { - min-width: 80px; -} diff --git a/src/settings/SettingsModal.tsx b/src/settings/SettingsModal.tsx index b3d25a5..03dd2ac 100644 --- a/src/settings/SettingsModal.tsx +++ b/src/settings/SettingsModal.tsx @@ -195,7 +195,7 @@ export const SettingsModal = (props: Props) => { key="profile" title={ <> - + {t("Profile")} } diff --git a/src/tabs/Tabs.module.css b/src/tabs/Tabs.module.css index 188747c..9ba6104 100644 --- a/src/tabs/Tabs.module.css +++ b/src/tabs/Tabs.module.css @@ -25,12 +25,14 @@ limitations under the License. list-style: none; padding: 0; margin: 0 auto 24px auto; + gap: 16px; + overflow: scroll; + max-width: 100%; } .tab { - max-width: 190px; - min-width: fit-content; height: 32px; + box-sizing: border-box; border-radius: 8px; background-color: transparent; display: flex; @@ -38,6 +40,7 @@ limitations under the License. padding: 0 8px; border: none; cursor: pointer; + font-size: var(--font-size-body); } .tab > * { @@ -78,17 +81,18 @@ limitations under the License. @media (min-width: 800px) { .tab { + width: 200px; padding: 0 16px; } .tab > * { - margin: 0 16px 0 0; + margin: 0 12px 0 0; } .tabContainer { width: 100%; flex-direction: row; - padding: 27px 20px; + padding: 20px 18px; box-sizing: border-box; overflow: hidden; } @@ -96,6 +100,7 @@ limitations under the License. .tabList { flex-direction: column; margin-bottom: 0; + gap: 0; } .tabPanel { From 69099772e02ec161b1bfe0401c57ff344d94a6ea Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Mon, 22 May 2023 13:44:53 -0400 Subject: [PATCH 09/15] Make settings button icon size match designs --- src/button/Button.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/button/Button.tsx b/src/button/Button.tsx index 1ac7921..289f2b9 100644 --- a/src/button/Button.tsx +++ b/src/button/Button.tsx @@ -238,7 +238,7 @@ export function SettingsButton({ return ( ); From 6560d9eb1a62a1403b059528801c0b0272b4b0ea Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Mon, 22 May 2023 13:51:05 -0400 Subject: [PATCH 10/15] Make remove avatar button target area larger --- src/input/AvatarInputField.module.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/input/AvatarInputField.module.css b/src/input/AvatarInputField.module.css index 1462042..626d110 100644 --- a/src/input/AvatarInputField.module.css +++ b/src/input/AvatarInputField.module.css @@ -54,4 +54,6 @@ limitations under the License. .removeButton { color: var(--accent); + font-size: var(--font-size-caption); + padding: 6px 0; } From 85380c814231df2309787754c32cdc53d1b809c7 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Mon, 22 May 2023 13:59:18 -0400 Subject: [PATCH 11/15] Make width of profile tab conform to designs --- src/settings/ProfileSettingsTab.module.css | 6 ++++++ src/settings/ProfileSettingsTab.tsx | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/settings/ProfileSettingsTab.module.css b/src/settings/ProfileSettingsTab.module.css index 4b80e49..e85a00a 100644 --- a/src/settings/ProfileSettingsTab.module.css +++ b/src/settings/ProfileSettingsTab.module.css @@ -14,6 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ +.content { + width: 100%; + max-width: 350px; + align-self: center; +} + .avatarFieldRow { justify-content: center; } diff --git a/src/settings/ProfileSettingsTab.tsx b/src/settings/ProfileSettingsTab.tsx index a645133..7e32568 100644 --- a/src/settings/ProfileSettingsTab.tsx +++ b/src/settings/ProfileSettingsTab.tsx @@ -74,7 +74,7 @@ export function ProfileSettingsTab({ client }: Props) { ); return ( -
+ Date: Mon, 22 May 2023 14:33:20 -0400 Subject: [PATCH 12/15] Make the profile form autosave --- src/input/Input.tsx | 1 + src/settings/ProfileSettingsTab.tsx | 84 +++++++++++++---------------- src/settings/useMediaHandler.tsx | 2 +- 3 files changed, 38 insertions(+), 49 deletions(-) diff --git a/src/input/Input.tsx b/src/input/Input.tsx index afecd6a..95d3fc5 100644 --- a/src/input/Input.tsx +++ b/src/input/Input.tsx @@ -72,6 +72,7 @@ interface InputFieldProps { autoCorrect?: string; autoCapitalize?: string; value?: string; + defaultValue?: string; placeholder?: string; defaultChecked?: boolean; onChange?: (event: ChangeEvent) => void; diff --git a/src/settings/ProfileSettingsTab.tsx b/src/settings/ProfileSettingsTab.tsx index 7e32568..023a7cd 100644 --- a/src/settings/ProfileSettingsTab.tsx +++ b/src/settings/ProfileSettingsTab.tsx @@ -14,11 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { ChangeEvent, useCallback, useState } from "react"; +import React, { useCallback, useEffect, useRef } from "react"; import { MatrixClient } from "matrix-js-sdk/src/client"; import { useTranslation } from "react-i18next"; -import { Button } from "../button"; import { useProfile } from "../profile/useProfile"; import { FieldRow, InputField, ErrorMessage } from "../input/Input"; import { AvatarInputField } from "../input/AvatarInputField"; @@ -29,52 +28,47 @@ interface Props { } export function ProfileSettingsTab({ client }: Props) { const { t } = useTranslation(); - const { - error, - loading, - displayName: initialDisplayName, - avatarUrl, - saveProfile, - } = useProfile(client); - const [displayName, setDisplayName] = useState(initialDisplayName || ""); - const [removeAvatar, setRemoveAvatar] = useState(false); + const { error, displayName, avatarUrl, saveProfile } = useProfile(client); - const onRemoveAvatar = useCallback(() => { - setRemoveAvatar(true); + const formRef = useRef(null); + + const formChanged = useRef(false); + const onFormChange = useCallback(() => { + formChanged.current = true; }, []); - const onChangeDisplayName = useCallback( - (e: ChangeEvent) => { - setDisplayName(e.target.value); - }, - [setDisplayName] - ); + const removeAvatar = useRef(false); + const onRemoveAvatar = useCallback(() => { + removeAvatar.current = true; + formChanged.current = true; + }, []); - const onSubmit = useCallback( - (e) => { - e.preventDefault(); - const data = new FormData(e.target); - const displayNameDataEntry = data.get("displayName"); - const avatar: File | string = data.get("avatar"); + useEffect(() => { + const form = formRef.current!; + return () => { + if (formChanged.current) { + const data = new FormData(form); + const displayNameDataEntry = data.get("displayName"); + const avatar = data.get("avatar"); - const avatarSize = - typeof avatar == "string" ? avatar.length : avatar.size; - const displayName = - typeof displayNameDataEntry == "string" - ? displayNameDataEntry - : displayNameDataEntry.name; + const avatarSize = + typeof avatar == "string" ? avatar.length : avatar?.size ?? 0; + const displayName = + typeof displayNameDataEntry == "string" + ? displayNameDataEntry + : displayNameDataEntry?.name ?? null; - saveProfile({ - displayName, - avatar: avatar && avatarSize > 0 ? avatar : undefined, - removeAvatar: removeAvatar && (!avatar || avatarSize === 0), - }); - }, - [saveProfile, removeAvatar] - ); + saveProfile({ + displayName, + avatar: avatar && avatarSize > 0 ? avatar : undefined, + removeAvatar: removeAvatar.current && (!avatar || avatarSize === 0), + }); + } + }; + }, [saveProfile]); return ( - + @@ -104,8 +98,7 @@ export function ProfileSettingsTab({ client }: Props) { required autoComplete="off" placeholder={t("Display name")} - value={displayName} - onChange={onChangeDisplayName} + defaultValue={displayName} data-testid="profile_displayname" /> @@ -114,11 +107,6 @@ export function ProfileSettingsTab({ client }: Props) { )} - - -
); } diff --git a/src/settings/useMediaHandler.tsx b/src/settings/useMediaHandler.tsx index bf62eeb..0d84996 100644 --- a/src/settings/useMediaHandler.tsx +++ b/src/settings/useMediaHandler.tsx @@ -25,8 +25,8 @@ import React, { ReactNode, useRef, } from "react"; -import { useClient } from "../ClientContext"; +import { useClient } from "../ClientContext"; import { getNamedDevices } from "../media-utils"; export interface MediaHandlerContextInterface { From dc8d0fd81bf261451a728121ab5c2b2cfff67d6c Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Mon, 22 May 2023 14:34:37 -0400 Subject: [PATCH 13/15] Update strings --- public/locales/en-GB/app.json | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/public/locales/en-GB/app.json b/public/locales/en-GB/app.json index 79d59fd..ccd111b 100644 --- a/public/locales/en-GB/app.json +++ b/public/locales/en-GB/app.json @@ -38,7 +38,6 @@ "Create account": "Create account", "Debug log": "Debug log", "Debug log request": "Debug log request", - "Description (optional)": "Description (optional)", "Details": "Details", "Developer": "Developer", "Developer Settings": "Developer Settings", @@ -47,14 +46,14 @@ "Element Call Home": "Element Call Home", "Exit full screen": "Exit full screen", "Expose developer settings in the settings window.": "Expose developer settings in the settings window.", - "feedback": "feedback", + "Feedback": "Feedback", "Fetching group call timed out.": "Fetching group call timed out.", "Freedom": "Freedom", "Full screen": "Full screen", "Go": "Go", "Grid layout menu": "Grid layout menu", - "Having trouble? Help us fix it.": "Having trouble? Help us fix it.", "Home": "Home", + "If you are experiencing issues or simply would like to provide some feedback, please send us a short description below.": "If you are experiencing issues or simply would like to provide some feedback, please send us a short description below.", "Include debug logs": "Include debug logs", "Incompatible versions": "Incompatible versions", "Incompatible versions!": "Incompatible versions!", @@ -94,8 +93,6 @@ "Release to stop": "Release to stop", "Remove": "Remove", "Return to home screen": "Return to home screen", - "Save": "Save", - "Saving…": "Saving…", "Select an option": "Select an option", "Send debug logs": "Send debug logs", "Sending debug logs…": "Sending debug logs…", @@ -110,11 +107,13 @@ "Speaker {{n}}": "Speaker {{n}}", "Spotlight": "Spotlight", "Stop sharing screen": "Stop sharing screen", + "Submit": "Submit", "Submit feedback": "Submit feedback", - "Submitting feedback…": "Submitting feedback…", + "Submitting…": "Submitting…", "Take me Home": "Take me Home", "Talk over speaker": "Talk over speaker", "Talking…": "Talking…", + "Thanks, we received your feedback!": "Thanks, we received your feedback!", "Thanks! We'll get right on it.": "Thanks! We'll get right on it.", "This call already exists, would you like to join?": "This call already exists, would you like to join?", "This feature is only supported on Firefox.": "This feature is only supported on Firefox.", @@ -124,7 +123,6 @@ "Turn on camera": "Turn on camera", "Unmute microphone": "Unmute microphone", "Use the upcoming grid system": "Use the upcoming grid system", - "User ID": "User ID", "User menu": "User menu", "Username": "Username", "Version: {{version}}": "Version: {{version}}", @@ -138,5 +136,6 @@ "WebRTC is not supported or is being blocked in this browser.": "WebRTC is not supported or is being blocked in this browser.", "Yes, join call": "Yes, join call", "You can't talk at the same time": "You can't talk at the same time", + "Your feedback": "Your feedback", "Your recent calls": "Your recent calls" } From 9c2f4be17c2272f4fdb4492c5b85844f84ad3657 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Mon, 22 May 2023 15:30:29 -0400 Subject: [PATCH 14/15] Bring back the rageshake request modal --- src/room/InCallView.tsx | 15 ++++++++++++++- src/room/PTTCallView.tsx | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/room/InCallView.tsx b/src/room/InCallView.tsx index d27dd2c..3446d4a 100644 --- a/src/room/InCallView.tsx +++ b/src/room/InCallView.tsx @@ -75,6 +75,8 @@ import { NewVideoGrid } from "../video-grid/NewVideoGrid"; import { OTelGroupCallMembership } from "../otel/OTelGroupCallMembership"; import { SettingsModal } from "../settings/SettingsModal"; import { InviteModal } from "./InviteModal"; +import { useRageshakeRequestModal } from "../settings/submit-rageshake"; +import { RageshakeRequestModal } from "./RageshakeRequestModal"; const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {}); // There is currently a bug in Safari our our code with cloning and sending MediaStreams @@ -340,6 +342,11 @@ export function InCallView({ ); }; + const { + modalState: rageshakeRequestModalState, + modalProps: rageshakeRequestModalProps, + } = useRageshakeRequestModal(groupCall.room.roomId); + const { modalState: settingsModalState, modalProps: settingsModalProps, @@ -469,10 +476,16 @@ export function InCallView({ otelGroupCallMembership={otelGroupCallMembership} show={showInspector} /> + {rageshakeRequestModalState.isOpen && !noControls && ( + + )} {settingsModalState.isOpen && ( )} diff --git a/src/room/PTTCallView.tsx b/src/room/PTTCallView.tsx index af5437d..44b4d74 100644 --- a/src/room/PTTCallView.tsx +++ b/src/room/PTTCallView.tsx @@ -309,7 +309,7 @@ export const PTTCallView: React.FC = ({ {settingsModalState.isOpen && ( )} From 3c118f0cf73bcf7cfae8f0a2ceb65483712ba466 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Mon, 22 May 2023 15:44:39 -0400 Subject: [PATCH 15/15] Add a comment --- src/settings/ProfileSettingsTab.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/settings/ProfileSettingsTab.tsx b/src/settings/ProfileSettingsTab.tsx index 023a7cd..409c0e6 100644 --- a/src/settings/ProfileSettingsTab.tsx +++ b/src/settings/ProfileSettingsTab.tsx @@ -45,6 +45,7 @@ export function ProfileSettingsTab({ client }: Props) { useEffect(() => { const form = formRef.current!; + // Auto-save when the user dismisses this component return () => { if (formChanged.current) { const data = new FormData(form);