diff --git a/src/room/FeedbackModal.jsx b/src/room/FeedbackModal.jsx new file mode 100644 index 00000000..ba07e2fb --- /dev/null +++ b/src/room/FeedbackModal.jsx @@ -0,0 +1,69 @@ +import React, { useCallback, useEffect } from "react"; +import { Modal, ModalContent } from "../Modal"; +import { Button } from "../button"; +import { FieldRow, InputField, ErrorMessage } from "../input/Input"; +import { useSubmitRageshake, useRageshakeRequest } from "../settings/rageshake"; +import { Body } from "../typography/Typography"; + +export function FeedbackModal({ inCall, roomId, ...rest }) { + const { submitRageshake, sending, sent, error } = useSubmitRageshake(); + const sendRageshakeRequest = useRageshakeRequest(); + + const onSubmitFeedback = useCallback( + (e) => { + e.preventDefault(); + const data = new FormData(e.target); + const description = data.get("description"); + const sendLogs = data.get("sendLogs"); + submitRageshake({ description, sendLogs }); + + if (inCall && sendLogs) { + sendRageshakeRequest(roomId); + } + }, + [inCall, submitRageshake, roomId, sendRageshakeRequest] + ); + + useEffect(() => { + if (sent) { + rest.onClose(); + } + }, [sent, rest.onClose]); + + return ( + + + Having trouble on this call? Help us fix it. +
+ + + + + + + {error && ( + + {error.message} + + )} + + + +
+
+
+ ); +} diff --git a/src/room/InCallView.jsx b/src/room/InCallView.jsx index 5a7db8b9..e28c7717 100644 --- a/src/room/InCallView.jsx +++ b/src/room/InCallView.jsx @@ -19,6 +19,8 @@ import { OverflowMenu } from "./OverflowMenu"; import { GridLayoutMenu } from "./GridLayoutMenu"; import { Avatar } from "../Avatar"; import { UserMenuContainer } from "../UserMenuContainer"; +import { useRageshakeRequestModal } from "../settings/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 @@ -120,6 +122,11 @@ export function InCallView({ [client] ); + const { + modalState: rageshakeRequestModalState, + modalProps: rageshakeRequestModalProps, + } = useRageshakeRequestModal(groupCall.room.roomId); + return (
@@ -164,10 +171,12 @@ export function InCallView({ /> )}
@@ -176,6 +185,9 @@ export function InCallView({ groupCall={groupCall} show={showInspector} /> + {rageshakeRequestModalState.isOpen && ( + + )} ); } diff --git a/src/room/OverflowMenu.jsx b/src/room/OverflowMenu.jsx index e35538d2..b507449b 100644 --- a/src/room/OverflowMenu.jsx +++ b/src/room/OverflowMenu.jsx @@ -9,18 +9,23 @@ import { ReactComponent as OverflowIcon } from "../icons/Overflow.svg"; import { useModalTriggerState } from "../Modal"; import { SettingsModal } from "../settings/SettingsModal"; import { InviteModal } from "./InviteModal"; -import { Tooltip, TooltipTrigger } from "../Tooltip"; +import { TooltipTrigger } from "../Tooltip"; +import { FeedbackModal } from "./FeedbackModal"; export function OverflowMenu({ roomId, setShowInspector, showInspector, client, + inCall, + groupCall, }) { const { modalState: inviteModalState, modalProps: inviteModalProps } = useModalTriggerState(); const { modalState: settingsModalState, modalProps: settingsModalProps } = useModalTriggerState(); + const { modalState: feedbackModalState, modalProps: feedbackModalProps } = + useModalTriggerState(); // TODO: On closing modal, focus should be restored to the trigger button // https://github.com/adobe/react-spectrum/issues/2444 @@ -32,6 +37,9 @@ export function OverflowMenu({ case "settings": settingsModalState.open(); break; + case "feedback": + feedbackModalState.open(); + break; } }); @@ -54,6 +62,10 @@ export function OverflowMenu({ Settings + + + Submit Feedback + )} @@ -68,6 +80,13 @@ export function OverflowMenu({ {inviteModalState.isOpen && ( )} + {feedbackModalState.isOpen && ( + + )} ); } diff --git a/src/room/RageshakeRequestModal.jsx b/src/room/RageshakeRequestModal.jsx new file mode 100644 index 00000000..2aeb5441 --- /dev/null +++ b/src/room/RageshakeRequestModal.jsx @@ -0,0 +1,40 @@ +import React, { useEffect } from "react"; +import { Modal, ModalContent } from "../Modal"; +import { Button } from "../button"; +import { FieldRow, ErrorMessage } from "../input/Input"; +import { useSubmitRageshake } from "../settings/rageshake"; +import { Body } from "../typography/Typography"; + +export function RageshakeRequestModal(props) { + const { submitRageshake, sending, sent, error } = useSubmitRageshake(); + + useEffect(() => { + if (sent) { + props.onClose(); + } + }, [sent, props.onClose]); + + return ( + + + + Another user on this call is having an issue. In order to better + diagnose these issues we'd like to collect a debug log. + + + + + {error && ( + + {error.message} + + )} + + + ); +} diff --git a/src/settings/SettingsModal.jsx b/src/settings/SettingsModal.jsx index 014f92a8..e456cff8 100644 --- a/src/settings/SettingsModal.jsx +++ b/src/settings/SettingsModal.jsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React from "react"; import { Modal } from "../Modal"; import styles from "./SettingsModal.module.css"; import { TabContainer, TabItem } from "../tabs/Tabs"; @@ -8,10 +8,9 @@ import { ReactComponent as DeveloperIcon } from "../icons/Developer.svg"; import { SelectInput } from "../input/SelectInput"; import { Item } from "@react-stately/collections"; import { useMediaHandler } from "./useMediaHandler"; -import { FieldRow, InputField, ErrorMessage } from "../input/Input"; +import { FieldRow, InputField } from "../input/Input"; import { Button } from "../button"; -import { useSubmitRageshake } from "./useSubmitRageshake"; -import { Subtitle } from "../typography/Typography"; +import { useDownloadDebugLog } from "./rageshake"; export function SettingsModal({ client, @@ -28,10 +27,7 @@ export function SettingsModal({ setVideoInput, } = useMediaHandler(client); - const [description, setDescription] = useState(""); - - const { submitRageshake, sending, sent, error, downloadDebugLog } = - useSubmitRageshake(); + const downloadDebugLog = useDownloadDebugLog(); return ( setShowInspector(e.target.checked)} /> - Feedback - - setDescription(e.target.value)} - /> - - - - - {error && ( - - {error.message} - - )} diff --git a/src/settings/useSubmitRageshake.js b/src/settings/rageshake.js similarity index 78% rename from src/settings/useSubmitRageshake.js rename to src/settings/rageshake.js index 0c32b8b2..d1d69c81 100644 --- a/src/settings/useSubmitRageshake.js +++ b/src/settings/rageshake.js @@ -1,8 +1,9 @@ -import { useCallback, useContext, useState } from "react"; +import { useCallback, useContext, useEffect, useState } from "react"; import * as rageshake from "matrix-react-sdk/src/rageshake/rageshake"; import pako from "pako"; import { useClient } from "../ClientContext"; import { InspectorContext } from "../room/GroupCallInspector"; +import { useModalTriggerState } from "../Modal"; export function useSubmitRageshake() { const { client } = useClient(); @@ -171,24 +172,26 @@ export function useSubmitRageshake() { } catch (e) {} } - const logs = await rageshake.getLogsForReport(); + if (opts.sendLogs) { + const logs = await rageshake.getLogsForReport(); - for (const entry of logs) { - // encode as UTF-8 - let buf = new TextEncoder().encode(entry.lines); + for (const entry of logs) { + // encode as UTF-8 + let buf = new TextEncoder().encode(entry.lines); - // compress - buf = pako.gzip(buf); + // compress + buf = pako.gzip(buf); - body.append("compressed-log", new Blob([buf]), entry.id); - } + body.append("compressed-log", new Blob([buf]), entry.id); + } - if (json) { - body.append( - "file", - new Blob([JSON.stringify(json)], { type: "text/plain" }), - "groupcall.txt" - ); + if (json) { + body.append( + "file", + new Blob([JSON.stringify(json)], { type: "text/plain" }), + "groupcall.txt" + ); + } } await fetch( @@ -209,6 +212,17 @@ export function useSubmitRageshake() { [client] ); + return { + submitRageshake, + sending, + sent, + error, + }; +} + +export function useDownloadDebugLog() { + const [{ json }] = useContext(InspectorContext); + const downloadDebugLog = useCallback(() => { const blob = new Blob([JSON.stringify(json)], { type: "application/json" }); const url = URL.createObjectURL(blob); @@ -222,7 +236,47 @@ export function useSubmitRageshake() { URL.revokeObjectURL(url); el.parentNode.removeChild(el); }, 0); - }); + }, [json]); - return { submitRageshake, sending, sent, error, downloadDebugLog }; + return downloadDebugLog; +} + +export function useRageshakeRequest() { + const { client } = useClient(); + + const sendRageshakeRequest = useCallback( + (roomId) => { + client.sendEvent(roomId, "org.matrix.rageshake_request", {}); + }, + [client] + ); + + return sendRageshakeRequest; +} + +export function useRageshakeRequestModal(roomId) { + const { modalState, modalProps } = useModalTriggerState(); + const { client } = useClient(); + + useEffect(() => { + const onEvent = (event) => { + const type = event.getType(); + + if ( + type === "org.matrix.rageshake_request" && + roomId === event.getRoomId() && + client.getUserId() !== event.getSender() + ) { + modalState.open(); + } + }; + + client.on("event", onEvent); + + return () => { + client.removeListener("event", onEvent); + }; + }, [modalState.open, roomId]); + + return { modalState, modalProps }; }