Settings improvements
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
This commit is contained in:
		
					parent
					
						
							
								515e00b763
							
						
					
				
			
			
				commit
				
					
						0269753f59
					
				
			
		
					 15 changed files with 380 additions and 470 deletions
				
			
		|  | @ -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,6 +56,7 @@ export default function App({ history }: AppProps) { | |||
|       {loaded ? ( | ||||
|         <Suspense fallback={null}> | ||||
|           <ClientProvider> | ||||
|             <MediaHandlerProvider> | ||||
|               <InspectorContextProvider> | ||||
|                 <Sentry.ErrorBoundary fallback={errorPage}> | ||||
|                   <OverlayProvider> | ||||
|  | @ -81,6 +83,7 @@ export default function App({ history }: AppProps) { | |||
|                   </OverlayProvider> | ||||
|                 </Sentry.ErrorBoundary> | ||||
|               </InspectorContextProvider> | ||||
|             </MediaHandlerProvider> | ||||
|           </ClientProvider> | ||||
|         </Suspense> | ||||
|       ) : ( | ||||
|  |  | |||
|  | @ -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({ | ||||
|  |  | |||
|  | @ -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<string>(); | ||||
| 
 | ||||
|   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 && <ProfileModal client={client} {...modalProps} />} | ||||
|       {modalState.isOpen && ( | ||||
|         <SettingsModal | ||||
|           client={client} | ||||
|           defaultTab={defaultSettingsTab} | ||||
|           {...modalProps} | ||||
|         /> | ||||
|       )} | ||||
|     </> | ||||
|   ); | ||||
| } | ||||
|  |  | |||
|  | @ -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 <Button>
 | ||||
|   [index: string]: unknown; | ||||
| }) { | ||||
|  | @ -257,7 +259,7 @@ export function InviteButton({ | |||
| 
 | ||||
|   return ( | ||||
|     <TooltipTrigger tooltip={tooltip}> | ||||
|       <Button variant="toolbar" {...rest}> | ||||
|       <Button variant={variant} {...rest}> | ||||
|         <AddUserIcon /> | ||||
|       </Button> | ||||
|     </TooltipTrigger> | ||||
|  |  | |||
|  | @ -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 <Modal>
 | ||||
|   [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 ( | ||||
|     <Modal | ||||
|       title={t("Submit feedback")} | ||||
|       isDismissable | ||||
|       onClose={onClose} | ||||
|       {...rest} | ||||
|     > | ||||
|       <ModalContent> | ||||
|         <Body>{t("Having trouble? Help us fix it.")}</Body> | ||||
|         <form onSubmit={onSubmitFeedback}> | ||||
|           <FieldRow> | ||||
|             <InputField | ||||
|               id="description" | ||||
|               name="description" | ||||
|               label={t("Description (optional)")} | ||||
|               type="textarea" | ||||
|             /> | ||||
|           </FieldRow> | ||||
|           <FieldRow> | ||||
|             <InputField | ||||
|               id="sendLogs" | ||||
|               name="sendLogs" | ||||
|               label={t("Include debug logs")} | ||||
|               type="checkbox" | ||||
|               defaultChecked | ||||
|             /> | ||||
|           </FieldRow> | ||||
|           {error && ( | ||||
|             <FieldRow> | ||||
|               <ErrorMessage error={error} /> | ||||
|             </FieldRow> | ||||
|           )} | ||||
|           <FieldRow> | ||||
|             <Button type="submit" disabled={sending}> | ||||
|               {sending ? t("Submitting feedback…") : t("Submit feedback")} | ||||
|             </Button> | ||||
|           </FieldRow> | ||||
|         </form> | ||||
|       </ModalContent> | ||||
|     </Modal> | ||||
|   ); | ||||
| } | ||||
|  | @ -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<HTMLDivElement | null>(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( | ||||
|           <OverflowMenu | ||||
|             key="4" | ||||
|             inCall | ||||
|             roomIdOrAlias={roomIdOrAlias} | ||||
|             groupCall={groupCall} | ||||
|             showInvite={joinRule === JoinRule.Public} | ||||
|             feedbackModalState={feedbackModalState} | ||||
|             feedbackModalProps={feedbackModalProps} | ||||
|           /> | ||||
|         ); | ||||
|         buttons.push(<SettingsButton key="4" onPress={openSettings} />); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|  | @ -434,7 +448,9 @@ export function InCallView({ | |||
|           </LeftNav> | ||||
|           <RightNav> | ||||
|             <GridLayoutMenu layout={layout} setLayout={setLayout} /> | ||||
|             <UserMenuContainer preventNavigation /> | ||||
|             {joinRule === JoinRule.Public && ( | ||||
|               <InviteButton variant="icon" onClick={openInvite} /> | ||||
|             )} | ||||
|           </RightNav> | ||||
|         </Header> | ||||
|       )} | ||||
|  | @ -448,12 +464,16 @@ export function InCallView({ | |||
|         otelGroupCallMembership={otelGroupCallMembership} | ||||
|         show={showInspector} | ||||
|       /> | ||||
|       {rageshakeRequestModalState.isOpen && ( | ||||
|         <RageshakeRequestModal | ||||
|           {...rageshakeRequestModalProps} | ||||
|           roomIdOrAlias={roomIdOrAlias} | ||||
|       {settingsModalState.isOpen && ( | ||||
|         <SettingsModal | ||||
|           client={client} | ||||
|           roomId={roomIdOrAlias} | ||||
|           {...settingsModalProps} | ||||
|         /> | ||||
|       )} | ||||
|       {inviteModalState.isOpen && ( | ||||
|         <InviteModal roomIdOrAlias={roomIdOrAlias} {...inviteModalProps} /> | ||||
|       )} | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
|  |  | |||
|  | @ -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 ( | ||||
|     <> | ||||
|       <PopoverMenuTrigger disableOnState> | ||||
|         <TooltipTrigger tooltip={tooltip} placement="top"> | ||||
|           <Button variant="toolbar"> | ||||
|             <OverflowIcon /> | ||||
|           </Button> | ||||
|         </TooltipTrigger> | ||||
|         {(props: JSX.IntrinsicAttributes) => ( | ||||
|           <Menu {...props} label={t("More menu")} onAction={onAction}> | ||||
|             {showInvite && ( | ||||
|               <Item key="invite" textValue={t("Invite people")}> | ||||
|                 <AddUserIcon /> | ||||
|                 <span>{t("Invite people")}</span> | ||||
|               </Item> | ||||
|             )} | ||||
|             <Item key="settings" textValue={t("Settings")}> | ||||
|               <SettingsIcon /> | ||||
|               <span>{t("Settings")}</span> | ||||
|             </Item> | ||||
|             {Config.get().rageshake?.submit_url && ( | ||||
|               <Item key="feedback" textValue={t("Submit feedback")}> | ||||
|                 <FeedbackIcon /> | ||||
|                 <span>{t("Submit feedback")}</span> | ||||
|               </Item> | ||||
|             )} | ||||
|           </Menu> | ||||
|         )} | ||||
|       </PopoverMenuTrigger> | ||||
|       {settingsModalState.isOpen && <SettingsModal {...settingsModalProps} />} | ||||
|       {inviteModalState.isOpen && ( | ||||
|         <InviteModal roomIdOrAlias={roomIdOrAlias} {...inviteModalProps} /> | ||||
|       )} | ||||
|       {feedbackModalState.isOpen && ( | ||||
|         <FeedbackModal | ||||
|           {...feedbackModalProps} | ||||
|           roomId={groupCall?.room.roomId} | ||||
|           inCall={inCall} | ||||
|         /> | ||||
|       )} | ||||
|     </> | ||||
|   ); | ||||
| } | ||||
|  | @ -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<Props> = ({ | |||
|   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<Props> = ({ | |||
|             /> | ||||
|           </div> | ||||
|           <div className={styles.footer}> | ||||
|             <OverflowMenu | ||||
|               inCall | ||||
|               roomIdOrAlias={roomIdOrAlias} | ||||
|               groupCall={groupCall} | ||||
|               showInvite={false} | ||||
|               feedbackModalState={feedbackModalState} | ||||
|               feedbackModalProps={feedbackModalProps} | ||||
|             /> | ||||
|             <SettingsButton onPress={() => settingsModalState.open()} /> | ||||
|             {!isEmbedded && <HangupButton onPress={onLeave} />} | ||||
|             <InviteButton onPress={() => inviteModalState.open()} /> | ||||
|           </div> | ||||
|  | @ -265,7 +259,7 @@ export const PTTCallView: React.FC<Props> = ({ | |||
|               <div className={styles.talkingInfo} /> | ||||
|             ))} | ||||
|           <PTTButton | ||||
|             enabled={!feedbackModalState.isOpen} | ||||
|             enabled={!inviteModalState.isOpen && !settingsModalState.isOpen} | ||||
|             showTalkOverError={showTalkOverError} | ||||
|             activeSpeakerUserId={activeSpeakerUserId} | ||||
|             activeSpeakerDisplayName={activeSpeakerDisplayName} | ||||
|  | @ -312,6 +306,13 @@ export const PTTCallView: React.FC<Props> = ({ | |||
|         </div> | ||||
|       </div> | ||||
| 
 | ||||
|       {settingsModalState.isOpen && ( | ||||
|         <SettingsModal | ||||
|           client={client} | ||||
|           roomId={roomIdOrAlias} | ||||
|           {...settingsModalProps} | ||||
|         /> | ||||
|       )} | ||||
|       {inviteModalState.isOpen && showControls && ( | ||||
|         <InviteModal roomIdOrAlias={roomIdOrAlias} {...inviteModalProps} /> | ||||
|       )} | ||||
|  |  | |||
|  | @ -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,7 +100,6 @@ export const RoomPage: FC = () => { | |||
|   } | ||||
| 
 | ||||
|   return ( | ||||
|     <MediaHandlerProvider client={client}> | ||||
|     <GroupCallLoader | ||||
|       client={client} | ||||
|       roomIdOrAlias={roomIdOrAlias} | ||||
|  | @ -110,6 +108,5 @@ export const RoomPage: FC = () => { | |||
|     > | ||||
|       {groupCallView} | ||||
|     </GroupCallLoader> | ||||
|     </MediaHandlerProvider> | ||||
|   ); | ||||
| }; | ||||
|  |  | |||
|  | @ -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 ( | ||||
|     <div className={styles.preview} ref={previewRef}> | ||||
|  | @ -95,17 +108,13 @@ export function VideoPreview({ | |||
|               muted={localVideoMuted} | ||||
|               onPress={toggleLocalVideoMuted} | ||||
|             /> | ||||
|             <OverflowMenu | ||||
|               roomIdOrAlias={roomIdOrAlias} | ||||
|               feedbackModalState={feedbackModalState} | ||||
|               feedbackModalProps={feedbackModalProps} | ||||
|               inCall={false} | ||||
|               groupCall={undefined} | ||||
|               showInvite={false} | ||||
|             /> | ||||
|             <SettingsButton onPress={openSettings} /> | ||||
|           </div> | ||||
|         </> | ||||
|       )} | ||||
|       {settingsModalState.isOpen && ( | ||||
|         <SettingsModal client={client} {...settingsModalProps} /> | ||||
|       )} | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
|  |  | |||
							
								
								
									
										93
									
								
								src/settings/FeedbackSettingsTab.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								src/settings/FeedbackSettingsTab.tsx
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 ( | ||||
|     <div> | ||||
|       <Body>{t("Having trouble? Help us fix it.")}</Body> | ||||
|       <form onSubmit={onSubmitFeedback}> | ||||
|         <FieldRow> | ||||
|           <InputField | ||||
|             id="description" | ||||
|             name="description" | ||||
|             label={t("Description (optional)")} | ||||
|             type="textarea" | ||||
|           /> | ||||
|         </FieldRow> | ||||
|         <FieldRow> | ||||
|           <InputField | ||||
|             id="sendLogs" | ||||
|             name="sendLogs" | ||||
|             label={t("Include debug logs")} | ||||
|             type="checkbox" | ||||
|             defaultChecked | ||||
|           /> | ||||
|         </FieldRow> | ||||
|         {error && ( | ||||
|           <FieldRow> | ||||
|             <ErrorMessage error={error} /> | ||||
|           </FieldRow> | ||||
|         )} | ||||
|         <FieldRow> | ||||
|           <Button type="submit" disabled={sending}> | ||||
|             {sending ? t("Submitting feedback…") : t("Submit feedback")} | ||||
|           </Button> | ||||
|         </FieldRow> | ||||
|       </form> | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
|  | @ -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. | ||||
|  | @ -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,15 +73,7 @@ export function ProfileModal({ client, ...rest }: Props) { | |||
|     [saveProfile, removeAvatar] | ||||
|   ); | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     if (success) { | ||||
|       onClose(); | ||||
|     } | ||||
|   }, [success, onClose]); | ||||
| 
 | ||||
|   return ( | ||||
|     <Modal title={t("Profile")} isDismissable {...rest}> | ||||
|       <ModalContent> | ||||
|     <form onSubmit={onSubmit}> | ||||
|       <FieldRow className={styles.avatarFieldRow}> | ||||
|         <AvatarInputField | ||||
|  | @ -127,15 +114,10 @@ export function ProfileModal({ client, ...rest }: Props) { | |||
|         </FieldRow> | ||||
|       )} | ||||
|       <FieldRow rightAlign> | ||||
|             <Button type="button" variant="secondary" onPress={onClose}> | ||||
|               Cancel | ||||
|             </Button> | ||||
|         <Button type="submit" disabled={loading}> | ||||
|           {loading ? t("Saving…") : t("Save")} | ||||
|         </Button> | ||||
|       </FieldRow> | ||||
|     </form> | ||||
|       </ModalContent> | ||||
|     </Modal> | ||||
|   ); | ||||
| } | ||||
|  | @ -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<string | undefined>(); | ||||
| 
 | ||||
|   const onSelectedTabChanged = useCallback( | ||||
|     (tab) => { | ||||
|       setSelectedTab(tab); | ||||
|     }, | ||||
|     [setSelectedTab] | ||||
|   ); | ||||
| 
 | ||||
|   const optInDescription = ( | ||||
|     <Caption> | ||||
|       <Trans> | ||||
|  | @ -87,8 +104,13 @@ export const SettingsModal = (props: Props) => { | |||
|       className={styles.settingsModal} | ||||
|       {...props} | ||||
|     > | ||||
|       <TabContainer className={styles.tabContainer}> | ||||
|       <TabContainer | ||||
|         onSelectionChange={onSelectedTabChanged} | ||||
|         selectedKey={selectedTab ?? props.defaultTab ?? "audio"} | ||||
|         className={styles.tabContainer} | ||||
|       > | ||||
|         <TabItem | ||||
|           key="audio" | ||||
|           title={ | ||||
|             <> | ||||
|               <AudioIcon width={16} height={16} /> | ||||
|  | @ -145,6 +167,7 @@ export const SettingsModal = (props: Props) => { | |||
|           </FieldRow> | ||||
|         </TabItem> | ||||
|         <TabItem | ||||
|           key="video" | ||||
|           title={ | ||||
|             <> | ||||
|               <VideoIcon width={16} height={16} /> | ||||
|  | @ -167,6 +190,29 @@ export const SettingsModal = (props: Props) => { | |||
|           </SelectInput> | ||||
|         </TabItem> | ||||
|         <TabItem | ||||
|           key="profile" | ||||
|           title={ | ||||
|             <> | ||||
|               <UserIcon width={16} height={16} /> | ||||
|               <span>{t("Profile")}</span> | ||||
|             </> | ||||
|           } | ||||
|         > | ||||
|           <ProfileSettingsTab client={props.client} /> | ||||
|         </TabItem> | ||||
|         <TabItem | ||||
|           key="feedback" | ||||
|           title={ | ||||
|             <> | ||||
|               <FeedbackIcon width={16} height={16} /> | ||||
|               <span>{t("feedback")}</span> | ||||
|             </> | ||||
|           } | ||||
|         > | ||||
|           <FeedbackSettingsTab roomId={props.roomId} /> | ||||
|         </TabItem> | ||||
|         <TabItem | ||||
|           key="more" | ||||
|           title={ | ||||
|             <> | ||||
|               <OverflowIcon width={16} height={16} /> | ||||
|  | @ -174,18 +220,10 @@ export const SettingsModal = (props: Props) => { | |||
|             </> | ||||
|           } | ||||
|         > | ||||
|           <h4>Analytics</h4> | ||||
|           <FieldRow> | ||||
|             <InputField | ||||
|               id="optInAnalytics" | ||||
|               type="checkbox" | ||||
|               checked={optInAnalytics} | ||||
|               description={optInDescription} | ||||
|               onChange={(event: React.ChangeEvent<HTMLInputElement>) => | ||||
|                 setOptInAnalytics(event.target.checked) | ||||
|               } | ||||
|             /> | ||||
|           </FieldRow> | ||||
|           <h4>Developer</h4> | ||||
|           <p> | ||||
|             Version: {(import.meta.env.VITE_APP_VERSION as string) || "dev"} | ||||
|           </p> | ||||
|           <FieldRow> | ||||
|             <InputField | ||||
|               id="developerSettingsTab" | ||||
|  | @ -200,9 +238,22 @@ export const SettingsModal = (props: Props) => { | |||
|               } | ||||
|             /> | ||||
|           </FieldRow> | ||||
|           <h4>Analytics</h4> | ||||
|           <FieldRow> | ||||
|             <InputField | ||||
|               id="optInAnalytics" | ||||
|               type="checkbox" | ||||
|               checked={optInAnalytics} | ||||
|               description={optInDescription} | ||||
|               onChange={(event: React.ChangeEvent<HTMLInputElement>) => | ||||
|                 setOptInAnalytics(event.target.checked) | ||||
|               } | ||||
|             /> | ||||
|           </FieldRow> | ||||
|         </TabItem> | ||||
|         {developerSettingsTab && ( | ||||
|           <TabItem | ||||
|             key="developer" | ||||
|             title={ | ||||
|               <> | ||||
|                 <DeveloperIcon width={16} height={16} /> | ||||
|  |  | |||
|  | @ -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( | ||||
|     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 { | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue