Add rageshake request modal

This commit is contained in:
Robert Long 2022-02-04 16:55:57 -08:00
parent ec447429c5
commit 6ec9e4b666
6 changed files with 216 additions and 51 deletions

View File

@ -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 (
<Modal title="Submit Feedback" isDismissable {...rest}>
<ModalContent>
<Body>Having trouble on this call? Help us fix it.</Body>
<form onSubmit={onSubmitFeedback}>
<FieldRow>
<InputField
id="description"
name="description"
label="Description (optional)"
type="text"
/>
</FieldRow>
<FieldRow>
<InputField
id="sendLogs"
name="sendLogs"
label="Include Debug Logs"
type="checkbox"
defaultChecked
/>
</FieldRow>
{error && (
<FieldRow>
<ErrorMessage>{error.message}</ErrorMessage>
</FieldRow>
)}
<FieldRow>
<Button type="submit" disabled={sending}>
{sending ? "Submitting feedback..." : "Submit Feedback"}
</Button>
</FieldRow>
</form>
</ModalContent>
</Modal>
);
}

View File

@ -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 (
<div className={styles.inRoom}>
<Header>
@ -164,10 +171,12 @@ export function InCallView({
/>
)}
<OverflowMenu
inCall
roomId={roomId}
setShowInspector={setShowInspector}
showInspector={showInspector}
client={client}
groupCall={groupCall}
/>
<HangupButton onPress={onLeave} />
</div>
@ -176,6 +185,9 @@ export function InCallView({
groupCall={groupCall}
show={showInspector}
/>
{rageshakeRequestModalState.isOpen && (
<RageshakeRequestModal {...rageshakeRequestModalProps} />
)}
</div>
);
}

View File

@ -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({
<SettingsIcon />
<span>Settings</span>
</Item>
<Item key="feedback" textValue="Submit Feedback">
<SettingsIcon />
<span>Submit Feedback</span>
</Item>
</Menu>
)}
</PopoverMenuTrigger>
@ -68,6 +80,13 @@ export function OverflowMenu({
{inviteModalState.isOpen && (
<InviteModal roomId={roomId} {...inviteModalProps} />
)}
{feedbackModalState.isOpen && (
<FeedbackModal
{...feedbackModalProps}
roomId={groupCall?.room.roomId}
inCall={inCall}
/>
)}
</>
);
}

View File

@ -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 (
<Modal title="Debug Log Request" isDismissable {...props}>
<ModalContent>
<Body>
Another user on this call is having an issue. In order to better
diagnose these issues we'd like to collect a debug log.
</Body>
<FieldRow>
<Button
onPress={() => submitRageshake({ sendLogs: true })}
disabled={sending}
>
{sending ? "Sending debug log..." : "Send debug log"}
</Button>
</FieldRow>
{error && (
<FieldRow>
<ErrorMessage>{error.message}</ErrorMessage>
</FieldRow>
)}
</ModalContent>
</Modal>
);
}

View File

@ -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 (
<Modal
@ -96,31 +92,6 @@ export function SettingsModal({
onChange={(e) => setShowInspector(e.target.checked)}
/>
</FieldRow>
<Subtitle>Feedback</Subtitle>
<FieldRow>
<InputField
id="description"
name="description"
label="Description"
type="text"
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
</FieldRow>
<FieldRow>
<Button onPress={() => submitRageshake({ description })}>
{sent
? "Debug Logs Sent"
: sending
? "Sending Debug Logs..."
: "Send Debug Logs"}
</Button>
</FieldRow>
{error && (
<FieldRow>
<ErrorMessage>{error.message}</ErrorMessage>
</FieldRow>
)}
<FieldRow>
<Button onPress={downloadDebugLog}>Download Debug Logs</Button>
</FieldRow>

View File

@ -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 };
}